diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 98ff9ec12fa..0906f3914da 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -37,3 +37,6 @@ PHILOSOPHY.md @jaekwon @moul CONTRIBUTING.md @jaekwon @moul LICENSE.md @jaekwon @moul .github/CODEOWNERS @jaekwon @moul + +# Documentation. +docs/* @gnolang/devrels \ No newline at end of file diff --git a/.github/golangci.yml b/.github/golangci.yml index 992398956b1..53141a3143d 100644 --- a/.github/golangci.yml +++ b/.github/golangci.yml @@ -1,9 +1,20 @@ run: - timeout: 5m + concurrency: 8 + timeout: 10m + issue-exit-code: 1 tests: true skip-dirs-use-default: true + modules-download-mode: readonly + allow-parallel-runners: false + go: "" + +output: + uniq-by-line: false + path-prefix: "" + sort-results: true linters: + fast: false disable-all: true enable: - whitespace # Tool for detection of leading and trailing whitespace @@ -21,12 +32,11 @@ linters: - gofmt # Whether the code was gofmt-ed - goimports # Unused imports - goconst # Repeated strings that could be replaced by a constant - #- forcetypeassert # Finds forced type assertions - dogsled # Checks assignments with too many blank identifiers (e.g. x, , , _, := f()) - #- dupl # Code clone detection - errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13 - gofumpt # Stricter gofmt + - unused # Checks Go code for unused constants, variables, functions and types linters-settings: gofmt: @@ -42,9 +52,20 @@ linters-settings: checks: [ "all", "-ST1022", "-ST1003" ] errorlint: asserts: false + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style issues: whole-files: true + max-issues-per-linter: 0 + max-same-issues: 0 + new: false + fix: false exclude-rules: - path: _test\.go linters: diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml index 8902a128b5d..c7f209687c4 100644 --- a/.github/workflows/auto-author-assign.yml +++ b/.github/workflows/auto-author-assign.yml @@ -15,4 +15,4 @@ jobs: assign-author: runs-on: ubuntu-latest steps: - - uses: toshimaru/auto-author-assign@v1.6.2 + - uses: toshimaru/auto-author-assign@v2.0.1 diff --git a/.github/workflows/contribs.yml b/.github/workflows/contribs.yml new file mode 100644 index 00000000000..939ac670710 --- /dev/null +++ b/.github/workflows/contribs.yml @@ -0,0 +1,35 @@ +name: contribs + +on: + push: + branches: [ "master" ] + pull_request: + paths: + - "contribs/**" + - ".github/workflows/contribs.yml" + - "gnovm/**.go" + - "gno.land/**.go" + - "tm2/**.go" + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + install: + strategy: + fail-fast: false + matrix: + goversion: # two latest versions + - "1.21.x" + program: + - "gnomd" + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.goversion }} + - run: make install ${{ matrix.program }} + working-directory: contribs diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index d4b3079d612..f69a27d15b6 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -50,7 +50,7 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno test --verbose ./examples + - run: go run ./gnovm/cmd/gno test --verbose ./examples/... lint: strategy: fail-fast: false @@ -73,3 +73,22 @@ jobs: - run: go run ./gnovm/cmd/gno lint --verbose ./examples/gno.land/r/gnoland - run: go run ./gnovm/cmd/gno lint --verbose ./examples/gno.land/r/system # TODO: track coverage + mod-tidy: + strategy: + fail-fast: false + matrix: + go-version: [ "1.21.x" ] + # unittests: TODO: matrix with contracts + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v4 + - run: | + GNO_CMD="$(pwd)/gnovm/cmd/gno" + # 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) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 8f57bec80a1..4e812eed5b7 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -21,9 +21,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: "cd misc/devdeps && make install" - - run: "cd misc/gendocs && make gen" - - run: "find docs/ -type f -ls" + - uses: actions/setup-go@v4 + with: + go-version: "1.21.x" + - run: "cd misc/gendocs && make install gen" - uses: actions/configure-pages@v3 id: pages - uses: actions/upload-pages-artifact@v2 diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 2b38f254a13..d305bed2dcd 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -60,7 +60,7 @@ jobs: - _test.gnoland - _test.gnokey - _test.pkgs - #- _test.gnoweb # this test should be rewritten to run an inmemory localnode + - _test.gnoweb runs-on: ubuntu-latest timeout-minutes: 15 steps: diff --git a/.github/workflows/gnovm.yml b/.github/workflows/gnovm.yml index 71b03b5ca05..5fa68ac305a 100644 --- a/.github/workflows/gnovm.yml +++ b/.github/workflows/gnovm.yml @@ -67,6 +67,8 @@ jobs: - _test.gnolang.other runs-on: ubuntu-latest timeout-minutes: 15 + env: + COVERAGE_DIR: "/tmp/coverage" steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v4 @@ -74,28 +76,59 @@ jobs: go-version: ${{ matrix.goversion }} - name: test working-directory: gnovm + env: + TXTARCOVERDIR: ${{ env.COVERAGE_DIR }} run: | + mkdir -p $COVERAGE_DIR + + # Setup testing environements variables export GOPATH=$HOME/go - export GOTEST_FLAGS="-v -p 1 -timeout=30m -coverprofile=coverage.out -covermode=atomic" + export GOTEST_FLAGS="-v -p 1 -timeout=30m -covermode=atomic -test.gocoverdir=$COVERAGE_DIR" + + # Run target test make ${{ matrix.args }} - uses: actions/upload-artifact@v3 if: ${{ runner.os == 'Linux' && matrix.goversion == '1.21.x' }} with: name: ${{runner.os}}-coverage-gnovm-${{ matrix.args}}-${{matrix.goversion}} - path: ./gnovm/coverage.out + path: ${{ env.COVERAGE_DIR }} upload-coverage: needs: test runs-on: ubuntu-latest + env: + COVERAGE_DATA: /tmp/coverage/coverage-raw + COVERAGE_OUTPUT: /tmp/coverage/coverage-out + COVERAGE_PROFILE: /tmp/coverage/coverage.txt steps: - - name: Download all previous coverage artifacts + - run: mkdir -p $COVERAGE_DATA $COVERAGE_OUTPUT + - name: Download all previous coverage data artifacts uses: actions/download-artifact@v3 with: - path: ${{ runner.temp }}/coverage + path: ${{ env.COVERAGE_DATA }} + - uses: actions/setup-go@v4 + with: + go-version: "1.21.x" + - name: Merge coverages + working-directory: ${{ env.COVERAGE_DATA }} + run: | + # Create coverage directory list separate by comma + export COVERAGE_DIRS="$(ls | tr '\n' ',' | sed s/,$//)" + + # Merge all coverage data directories from previous tests + go tool covdata merge -v 1 -i="$COVERAGE_DIRS" -o $COVERAGE_OUTPUT + + # Print coverage percent for debug purpose if needed + echo 'coverage results:' + go tool covdata percent -i=$COVERAGE_OUTPUT + + # Generate coverage profile + go tool covdata textfmt -v 1 -i=$COVERAGE_OUTPUT -o $COVERAGE_PROFILE + - name: Upload combined coverage to Codecov uses: codecov/codecov-action@v3 with: - directory: ${{ runner.temp }}/coverage + files: ${{ env.COVERAGE_PROFILE }} token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: ${{ github.repository == 'gnolang/gno' }} diff --git a/.github/workflows/misc.yml b/.github/workflows/misc.yml index a7c20d61ffd..f72a97c95b3 100644 --- a/.github/workflows/misc.yml +++ b/.github/workflows/misc.yml @@ -71,8 +71,9 @@ jobs: - name: Check go.mods run: | - sums="$(sha256sum go.mod misc/devdeps/go.mod)" - for path in . ./misc/devdeps; do + gomods=$(find . -type f -name go.mod) + sums="$(sha256sum ${gomods})" + for path in $(dirname $(gomods)); do env -C $path go mod tidy -v || exit 1 done echo "$sums" | sha256sum -c diff --git a/.github/workflows/monthly-snapshots.yml b/.github/workflows/monthly-snapshots.yml new file mode 100644 index 00000000000..85307074218 --- /dev/null +++ b/.github/workflows/monthly-snapshots.yml @@ -0,0 +1,22 @@ +name: Monthly Snapshots + +on: + schedule: + - cron: '0 0 1 * *' + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Generate tag name + id: tag_name + run: echo "::set-output name=tag_name::v0.0.1-dev.$(date +'%Y.%m.%d')" + - name: Release + uses: softprops/action-gh-release@v1 + with: + generate_release_notes: true + prerelease: true + tag_name: '${{ steps.tag_name.outputs.tag_name }}' diff --git a/.github/workflows/tm2.yml b/.github/workflows/tm2.yml index 7b78ccb1e0f..d5c6d9ddda6 100644 --- a/.github/workflows/tm2.yml +++ b/.github/workflows/tm2.yml @@ -55,7 +55,7 @@ jobs: - _test.pkg.bft - _test.pkg.others runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 21 steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v4 @@ -65,7 +65,7 @@ jobs: working-directory: tm2 run: | export GOPATH=$HOME/go - export GOTEST_FLAGS="-v -p 1 -timeout=30m -coverprofile=coverage.out -covermode=atomic" + export GOTEST_FLAGS="-v -p 1 -timeout=20m -coverprofile=coverage.out -covermode=atomic" make ${{ matrix.args }} touch coverage.out - uses: actions/upload-artifact@v3 diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000000..2c81b9938ea --- /dev/null +++ b/.mailmap @@ -0,0 +1,12 @@ +# man 5 gitmailmap +# git log --mailmap --pretty=short | grep ^Author: | sort -u +Jae Kwon <53785+jaekwon@users.noreply.github.com> Jae Kwon +Jae Kwon <53785+jaekwon@users.noreply.github.com> Jae Kwon +Jae Kwon <53785+jaekwon@users.noreply.github.com> jaekwon +Jae Kwon <53785+jaekwon@users.noreply.github.com> Naut Jae +Thomas Bruyelle Thomas Bruyelle +Thomas Bruyelle Thomas Bruyelle +Miloš Živković Miloš Živković +Hariom Verma Hariom Verma +Giancarlos Salas Giancarlos Salas +Morgan Morgan diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1e23f18273..5c2bb8f9996 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ -# Contributing to GNO +# Contributing to Gno -Thank you for looking to contribute to the GNO project. -We appreciate every open-source contribution, as it helps us improve and enhance gno for the benefit of the community. +Thank you for looking to contribute to the Gno project. +We appreciate every open-source contribution, as it helps us improve and enhance Gno for the benefit of the community. This document outlines some basic pointers on making your future contribution a great experience. It outlines basic PR etiquette employed by the core gno team. It lays out coding styles, simple how-to guides and tools to get you up and @@ -20,7 +20,7 @@ Likewise, if you have an idea on how to improve this guide, go for it as well. - [Testing](#testing) - [Running locally](#running-locally) - [Running test workflows](#running-test-workflows) - - [Testing GNO code](#testing-gno-code) + - [Testing Gno code](#testing-gno-code) - [Repository Structure](#repository-structure) - [How do I?](#how-do-i) - [How do I submit changes?](#how-do-i-submit-changes) @@ -41,9 +41,9 @@ Likewise, if you have an idea on how to improve this guide, go for it as well. - **[Discord](https://discord.gg/YFtMjWwUN7)** - we are very active on Discord. Join today and start discussing all things gno with fellow engineers and enthusiasts. -- **[Awesome GNO](https://github.com/gnolang/awesome-gno)** - check out the list of compiled resources for helping you +- **[Awesome Gno](https://github.com/gnolang/awesome-gno)** - check out the list of compiled resources for helping you understand the gno ecosystem -- **[Active Staging](https://gno.land/)** - use the currently available staging environment to play around with a +- **[Active Staging](https://staging.gno.land/)** - use the currently available staging environment to play around with a production network. If you want to interact with a local instance, refer to the [Local Setup](#local-setup) guide. - **[Twitter](https://twitter.com/_gnoland)** - follow us on Twitter to get the latest scoop - **[Telegram](https://t.me/gnoland)** - join our official Telegram group to start a conversation about gno @@ -52,13 +52,12 @@ Likewise, if you have an idea on how to improve this guide, go for it as well. ### Environment -The gno repository is primarily based on Golang (Go), and Gnolang (Gno). +The gno repository is primarily based on Golang (Go) and Gnolang (Gno). The primary tech stack for working on the repository: - Go (version 1.20+) - make (for using Makefile configurations) -- Docker (for using the official Docker setup files) It is recommended to work on a Unix environment, as most of the tooling is built around ready-made tools in Unix (WSL2 for Windows / Linux / macOS). @@ -67,13 +66,21 @@ For Gno, there is no specific tooling that needs to be installed, that’s not a You can utilize the `gno` command to facilitate Gnolang support when writing Smart Contracts in Gno, by installing it with `make install_gno`. +If you are working on Go source code on this repository, `pkg.go.dev` will not +render our documentation as it has a license it does not recognise. Instead, use +the `go doc` command, or use our statically-generated documentation: +https://gnolang.github.io/gno/github.com/gnolang/gno.html + Additionally, you can also configure your editor to recognize `.gno` files as `.go` files, to get the benefit of syntax highlighting. -Currently, we support a [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=harry-hov.gno) extension -(eventually official in the future) for Gnolang. +#### Visual Studio Code + +There currently is an unofficial [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=harry-hov.gno) +extension (primarily developed by a core team member) for working with `*.gno` +files. -#### ViM Support +#### ViM Support (without LSP) Add to your `.vimrc` file: @@ -97,9 +104,68 @@ To use *gofumpt* instead of *gofmt*, as hinted in the comment, you may either ha cexpr system('go run -modfile /misc/devdeps/go.mod mvdan.cc/gofumpt -w ' . expand('%')) ``` +### ViM Support (with LSP) + There is an experimental and unofficial [Gno Language Server](https://github.com/jdkato/gnols) developed by the community, with an installation guide for Neovim. +For ViM purists, you have to install the [`vim-lsp`](https://github.com/prabirshrestha/vim-lsp) +plugin and then register the LSP server in your `.vimrc` file: + +```vim +augroup gno_autocmd + autocmd! + autocmd BufNewFile,BufRead *.gno + \ set filetype=gno | + \ set syntax=go +augroup END + +if (executable('gnols')) + au User lsp_setup call lsp#register_server({ + \ 'name': 'gnols', + \ 'cmd': ['gnols'], + \ 'allowlist': ['gno'], + \ 'config': {}, + \ 'workspace_config': { + \ 'root' : '/path/to/gno_repo', + \ 'gno' : '/path/to/gno_bin', + \ 'precompileOnSave' : v:true, + \ 'buildOnSave' : v:false, + \ }, + \ 'languageId': {server_info->'gno'}, + \ }) +else + echomsg 'gnols binary not found: LSP disabled for Gno files' +endif + +function! s:on_lsp_buffer_enabled() abort + " Autocompletion + setlocal omnifunc=lsp#complete + " Format on save + autocmd BufWritePre LspDocumentFormatSync + " Some optionnal mappings + nmap i (lsp-hover) + " Following mappings are not supported yet by gnols + " nmap gd (lsp-definition) + " nmap rr (lsp-rename) +endfunction +augroup lsp_install + au! + autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled() +augroup END +``` + +Note that unlike the previous ViM setup without LSP, here it is required by +`vim-lsp` to have a specific `filetype=gno`. Syntax highlighting is preserved +thanks to `syntax=go`. + +Inside `lsp#register_server()`, you also have to replace +`workspace_config.root` and `workspace_config.gno` with the correct directories +from your machine. + +Additionaly, it's not possible to use `gofumpt` for code formatting with +`gnols` for now. + #### Emacs Support 1. Install [go-mode.el](https://github.com/dominikh/go-mode.el). @@ -122,9 +188,36 @@ Clone the repo: `git clone https://github.com/gnolang/gno.git` Build / install base commands: -`make build ` +`make install` + +If you haven't already, you may need to add the directory where [`go install` +places its binaries](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies) +to your `PATH`. If you haven't configured `GOBIN` or `GOPATH` differently, this +command should suffice: + +``` +echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.profile +source ~/.profile # reload ~/.profile in the current shell +``` + +After that, you should be good to go to use `gno` and `gnokey`, straight from +your command line! The following commands should list the help messages for +each: -That’s it! +```console +$ gno --help +USAGE + [flags] [...] + +Runs the gno development toolkit +[...] +$ gnokey --help +USAGE + [flags] [...] + +Manages private keys for the node +[...] +``` ### Testing @@ -151,7 +244,7 @@ To run the entire test suite through workflow files, run the following command: act -v -j go-test -#### Testing GNO code +#### Testing Gno code If you wish to test a `.gno` Realm or Package, you can utilize the `gno` tool. @@ -169,24 +262,35 @@ subcommands by running: gno --help +#### Adding new tests + +Most packages will follow the convention established with Go: each package +contains within its file many files suffixed with `_test.go` which test its +functionality. As a general rule, you should follow this convention, and in +every PR you make you should ensure all the code you added is appropriately +covered by tests ([Codecov](https://about.codecov.io/) will loudly complain in +your PR's comments if you don't). + +Additionally, we have a few testing systems that stray from this general rule; +at the time of writing, these are for integration tests and language tests. You +can find more documentation about them [on this guide](gno/docs/testing-guide.md). + ### Repository Structure The repository structure can seem tricky at first, but it’s simple if you consider the philosophy that the gno project -employs (check out [PHILOSOPHY.md](https://github.com/gnolang/gno/blob/master/PHILOSOPHY.md)). +employs (check out [PHILOSOPHY.md](./PHILOSOPHY.md)). The gno project currently favors a mono-repo structure, as it’s easier to manage contributions and keep everyone aligned. In the future, this may change, but in the meantime the majority of gno resources and source code will be centralized here. -- `cmd` - contains the base command implementations for tools like `gnokey`, `gnotxport`, etc. The actual underlying - logic is located within the `pkgs` subdirectories. - `examples` - contains the example `.gno` realms and packages. This is the central point for adding user-defined realms and packages. -- `gnoland` - contains the base source code for bootstrapping the Gnoland node -- `pkgs` - contains the dev-audited packages used throughout the gno codebase -- `stdlibs` - contains the standard library packages used (imported) in `.gno` Smart Contracts. These packages are - themselves `.gno` files. -- `tests` - contains the standard language tests for Gnolang +- `gno.land` - contains the base source code for bootstrapping the Gnoland node, + using `tm2` and `gnovm`. +- `gnovm` - contains the implementation of the Gno programming language and its + Virtual Machine, together with their standard libraries and tests. +- `tm2` - contains a fork of the [Tendermint consensus engine](https://docs.tendermint.com/v0.34/introduction/what-is-tendermint.html) with different expectations. ## How do I? diff --git a/PHILOSOPHY.md b/PHILOSOPHY.md index c55d479f741..bde27a48ef7 100644 --- a/PHILOSOPHY.md +++ b/PHILOSOPHY.md @@ -5,7 +5,7 @@ * Readability is paramount - beautiful is better than fast. * Minimal code - keep total footprint small. * Minimal dependencies - all dependencies must get audited, and become part of the repo. - * Modular dependencies - whereever reasonable, make components modular. + * Modular dependencies - wherever reasonable, make components modular. * Finished - software projects that don't become finished are projects that are forever vulnerable. One of the primary goals of the Gno language and related works is to become finished within a reasonable timeframe. diff --git a/README.md b/README.md index 99634f90a0d..71c53b86f19 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,64 @@ # Gno -At first, there was Bitcoin, out of entropy soup of the greater All. -Then, there was Ethereum, which was created in the likeness of Bitcoin, -but made Turing complete. +> At first, there was Bitcoin, out of entropy soup of the greater All. +> Then, there was Ethereum, which was created in the likeness of Bitcoin, +> but made Turing complete. +> +> Among these were Tendermint and Cosmos to engineer robust PoS and IBC. +> Then came Gno upon Cosmos and there spring forth Gnoland, +> simulated by the Gnomes of the Greater Resistance. + +Gno is an interpreted and fully-deterministic implementation of the Go +programming language, designed to build succinct and composable smart contracts. +The first blockchain to use it is Gno.land, a +[Proof of Contribution](./docs/proof-of-contribution.md)-based chain, backed by +a variation of the [Tendermint](https://docs.tendermint.com/v0.34/introduction/what-is-tendermint.html) +consensus engine. -Among these were Tendermint and Cosmos to engineer robust PoS and IBC. -Then came Gno upon Cosmos and there spring forth Gnoland, -simulated by the Gnomes of the Greater Resistance. +## Getting started + +If you haven't already, take a moment to check out our [website](https://gno.land/). + +> The website is a deployment of our [gnoweb](./gno.land/cmd/gnoweb) frontend; you +> can use it to check out [some](https://test3.gno.land/r/demo/boards) [example](https://test3.gno.land/r/gnoland/blog) +> [contracts](https://test3.gno.land/r/demo/users). +> +> Use the `[source]` button in the header to inspect the program's source; use +> the `[help]` button to view how you can use [`gnokey`](./gno.land/cmd/gnokey) +> to interact with the chain from your command line. + +If you have already played around with the website, use our +[Getting Started](https://github.com/gnolang/getting-started) guide to learn how +to write and deploy your first smart contract. No local set-up required! + +Once you're done, learn how to set up your local environment with the +[quickstart guide](./examples/gno.land/r/demo/boards/README.md) and the +[contributing guide](./CONTRIBUTING.md). -## Discover +You can find out more existing tools & documentation for Gno on our +[awesome-gno](https://github.com/gnolang/awesome-gno) repository. +We look forward to seeing your first PR! + +## Repository structure * [examples](./examples) - smart-contract examples and guides for new Gno developers. * [gnovm](./gnovm) - GnoVM and Gnolang. * [gno.land](./gno.land) - Gno.land blockchain and tools. * [tm2](./tm2) - Tendermint2. -## Getting started - -Start your journey with Gno.land by: -- using the [`gnoweb`](./gno.land/cmd/gnoweb) interface on the [latest testnet (test3.gno.land)](https://test3.gno.land/), -- sending transactions with [`gnokey`](./gno.land/cmd/gnokey), -- writing smart-contracts with [`gno` (ex `gnodev`)](./gnovm/cmd/gno). - -Also, see the [quickstart guide](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/boards/README.md). +## Socials & Contact -## Contact - - * Discord: https://discord.gg/YFtMjWwUN7 <-- join now - * Gnoland: https://gno.land/r/demo/boards:testboard - * Telegram: https://t.me/gnoland - * Twitter: https://twitter.com/_gnoland +* [**Discord**](https://discord.gg/YFtMjWwUN7): good for general chat-based + conversations, as well as for asking support on developing with Gno. +* [**Reddit**](https://www.reddit.com/r/gnoland): more "permanent" and + forum-style discussions. Feel free to post anything Gno-related, as well as + any question related to Gno programming! +* [**Telegram**](https://t.me/gnoland): unofficial Telegram group. +* [**Twitter**](https://twitter.com/_gnoland): official Twitter account. Follow + us to know about new developments, events & official announcements about Gno! +* [**YouTube**](https://www.youtube.com/@_gnoland): here we post all of our + video content, like workshops, talks and public development calls. Follow + along on our development journey!
Short doc about all the commands @@ -52,28 +81,28 @@ Also, see the [quickstart guide](https://github.com/gnolang/gno/blob/master/exam
CI/CD/Tools badges and links GitHub Actions: - + * [![gno.land](https://github.com/gnolang/gno/actions/workflows/gnoland.yml/badge.svg)](https://github.com/gnolang/gno/actions/workflows/gnoland.yml) * [![gnovm](https://github.com/gnolang/gno/actions/workflows/gnovm.yml/badge.svg)](https://github.com/gnolang/gno/actions/workflows/gnovm.yml) * [![tm2](https://github.com/gnolang/gno/actions/workflows/tm2.yml/badge.svg)](https://github.com/gnolang/gno/actions/workflows/tm2.yml) * [![examples](https://github.com/gnolang/gno/actions/workflows/examples.yml/badge.svg)](https://github.com/gnolang/gno/actions/workflows/examples.yml) * [![docker](https://github.com/gnolang/gno/actions/workflows/docker.yml/badge.svg)](https://github.com/gnolang/gno/actions/workflows/docker.yml) - + Codecov: - + * General: [![codecov](https://codecov.io/gh/gnolang/gno/branch/master/graph/badge.svg?token=HPP82HR1P4)](https://codecov.io/gh/gnolang/gno) * tm2: [![codecov](https://codecov.io/gh/gnolang/gno/branch/master/graph/badge.svg?token=HPP82HR1P4&flag=tm2)](https://codecov.io/gh/gnolang/gno/tree/master/tm2) * gnovm: [![codecov](https://codecov.io/gh/gnolang/gno/branch/master/graph/badge.svg?token=HPP82HR1P4&flag=gnovm)](https://codecov.io/gh/gnolang/gno/tree/master/gnovm) * gno.land: [![codecov](https://codecov.io/gh/gnolang/gno/branch/master/graph/badge.svg?token=HPP82HR1P4&flag=gno.land)](https://codecov.io/gh/gnolang/gno/tree/master/gno.land) * examples: TODO - + Go Report Card: - + * [![Go Report Card](https://goreportcard.com/badge/github.com/gnolang/gno)](https://goreportcard.com/report/github.com/gnolang/gno) * tm2, gnovm, gno.land: TODO (blocked by tm2 split, because we need go mod workspaces) - + Pkg.go.dev - - * [![Go Reference](https://pkg.go.dev/badge/github.com/gnolang/gno.svg)](https://pkg.go.dev/github.com/gnolang/gno) - * TODO: host custom docs on gh-pages, to bypass license limitation + + * [![Go Reference](https://pkg.go.dev/badge/hey/google)](https://gnolang.github.io/gno/github.com/gnolang/gno.html) \ + (pkg.go.dev will not show our repository as it has a license it doesn't recognise)
diff --git a/contribs/Makefile b/contribs/Makefile new file mode 100644 index 00000000000..b816987b733 --- /dev/null +++ b/contribs/Makefile @@ -0,0 +1,30 @@ +.PHONY: help +help: + @echo "Available make commands:" + @cat Makefile | grep '^[a-z][^:]*:' | cut -d: -f1 | sort | sed 's/^/ /' + +.PHONY: install +install: install.gnomd + +install.gnomd:; cd gnomd && go install . + +.PHONY: clean +clean: + rm -rf build + +######################################## +# Dev tools +rundep=go run -modfile ../misc/devdeps/go.mod + +.PHONY: fmt +GOFMT_FLAGS ?= -w +fmt: + $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) . + +######################################## +# Test suite +GOTEST_FLAGS ?= -v -p 1 -timeout=30m + +.PHONY: test +test: + @echo "nothing to do." diff --git a/contribs/README.md b/contribs/README.md new file mode 100644 index 00000000000..c481af0f4aa --- /dev/null +++ b/contribs/README.md @@ -0,0 +1,17 @@ +# Gno Contribs + +This directory houses additional commands and tools designed to enhance your Gno experience. +These tools can range from simple wrappers for `gno`, `gnoland`, and `gnokey` to complete applications. +Some may be Go binaries with their own `go.mod` files, allowing the use of specialized libraries, +while others may be shell scripts or programs written in different languages. + +## Contributing Guidelines + +If you'd like to contribute a tool to Gno Contribs, please follow these guidelines: + +1. **Naming**: Choose a clear and concise name for your tool, starting with one of the prefixes: `gno`, `gnoland`, or `gnokey`, + followed by a short descriptive word or abbreviation. + This naming convention helps users identify the purpose of the tool easily. + +2. **User-Friendly**: Ensure that your tool is user-friendly and follows a similar style to other Gno tools, + providing a consistent experience for users. diff --git a/contribs/gnokeykc/README.md b/contribs/gnokeykc/README.md new file mode 100644 index 00000000000..5d508b4b221 --- /dev/null +++ b/contribs/gnokeykc/README.md @@ -0,0 +1,16 @@ +# `gnokeykc` + +`gnokeykc` is a Go-based CLI tool that enhances [`gnokey`](../../gno.land/cmd/gnokey) by integrating with your system's keychain. It adds `gnokey kc ...` subcommands to set and unset passwords in the keychain, allowing Gnokey to fetch passwords directly from the keychain instead of prompting for terminal input. + +## Usage + + gnokey kc -h + +## Terminal Alias + +For ease of use, set up a terminal alias to replace `gnokey` with `gnokeykc`: + + echo "alias gnokey='gnokeykc'" >> ~/.bashrc && source ~/.bashrc + +Now, `gnokey` commands will use `gnokeykc`, fetching passwords from the keychain. + diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod new file mode 100644 index 00000000000..fca27e7455a --- /dev/null +++ b/contribs/gnokeykc/go.mod @@ -0,0 +1,54 @@ +module github.com/gnolang/gno/contribs/gnokeykc + +go 1.20 + +replace github.com/gnolang/gno => ../.. + +require ( + github.com/gnolang/gno v0.0.0-00010101000000-000000000000 + github.com/zalando/go-keyring v0.2.3 +) + +require ( + github.com/alessio/shellescape v1.4.1 // indirect + github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c // indirect + github.com/btcsuite/btcd/btcutil v1.0.0 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/cockroachdb/apd v1.1.0 // indirect + github.com/danieljoos/wincred v1.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgraph-io/badger/v3 v3.2103.4 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/gnolang/goleveldb v0.0.9 // indirect + github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/flatbuffers v1.12.1 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/jmhodges/levigo v1.0.0 // indirect + github.com/klauspost/compress v1.12.3 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/linxGnu/grocksdb v1.8.4 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/peterbourgon/ff/v3 v3.4.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rs/cors v1.10.1 // indirect + github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect + go.etcd.io/bbolt v1.3.7 // indirect + go.opencensus.io v0.22.5 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum new file mode 100644 index 00000000000..9ab29663ae6 --- /dev/null +++ b/contribs/gnokeykc/go.sum @@ -0,0 +1,237 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c h1:lnAMg3ra/Gw4AkRMxrxYs8nrprWsHowg8H9zaYsJOo4= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd/btcutil v1.0.0 h1:dB36qRTOucIh6NUe40UCieOS+axPhP6VNyRtYkTUKKk= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v3 v3.2103.4 h1:WE1B07YNTTJTtG9xjBcSW2wn0RJLyiV99h959RKZqM4= +github.com/dgraph-io/badger/v3 v3.2103.4/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gnolang/goleveldb v0.0.9 h1:Q7rGko9oXMKtQA+Apeeed5a3sjba/mcDhzJGoTVLCKE= +github.com/gnolang/goleveldb v0.0.9/go.mod h1:Dz6p9bmpy/FBESTgduiThZt5mToVDipcHGzj/zUOo8E= +github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= +github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jaekwon/testify v1.6.1 h1:4AtAJcR9GzXN5W4DdY7ie74iCPiJV1JJUJL90t2ZUyw= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/linxGnu/grocksdb v1.8.4 h1:ZMsBpPpJNtRLHiKKp0mI7gW+NT4s7UgfD5xHxx1jVRo= +github.com/linxGnu/grocksdb v1.8.4/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= +github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= +github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/contribs/gnokeykc/kc.go b/contribs/gnokeykc/kc.go new file mode 100644 index 00000000000..f91dc8ba4dd --- /dev/null +++ b/contribs/gnokeykc/kc.go @@ -0,0 +1,94 @@ +package main + +import ( + "context" + "flag" + "fmt" + + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/zalando/go-keyring" +) + +const ( + kcService = "gnokey" + kcName = "encryption" +) + +func newKcCmd(io commands.IO) *commands.Command { + cmd := commands.NewCommand( + commands.Metadata{ + Name: "kc", + ShortUsage: "kc ", + ShortHelp: "Manage OS keychain", + }, + commands.NewEmptyConfig(), + commands.HelpExec, + ) + cmd.AddSubCommands( + newKcSetCmd(io), + newKcUnsetCmd(io), + ) + return cmd +} + +func newKcSetCmd(io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "set", + ShortUsage: "set", + ShortHelp: "set encryption password in OS keychain", + }, + commands.NewEmptyConfig(), + func(_ context.Context, args []string) error { + return execKcSet(args, io) + }, + ) +} + +func execKcSet(args []string, io commands.IO) error { + if len(args) != 0 { + return flag.ErrHelp + } + + insecurePasswordStdin := false // XXX: cfg.rootCfg.InsecurePasswordStdin + password, err := io.GetPassword("Enter password.", insecurePasswordStdin) + if err != nil { + return fmt.Errorf("cannot read password: %w", err) + } + + err = keyring.Set(kcService, kcName, password) + if err != nil { + return fmt.Errorf("cannot set password is OS keychain") + } + + io.Printfln("Successfully added password for key.") + return nil +} + +func newKcUnsetCmd(io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "unset", + ShortUsage: "unset", + ShortHelp: "unset password in OS keychain", + }, + commands.NewEmptyConfig(), + func(_ context.Context, args []string) error { + return execKcUnset(args, io) + }, + ) +} + +func execKcUnset(args []string, io commands.IO) error { + if len(args) != 0 { + return flag.ErrHelp + } + + err := keyring.Delete(kcService, kcName) + if err != nil { + return fmt.Errorf("cannot unset password from OS keychain") + } + + io.Printfln("Successfully unset password") + return nil +} diff --git a/contribs/gnokeykc/main.go b/contribs/gnokeykc/main.go new file mode 100644 index 00000000000..8060f8cb1e3 --- /dev/null +++ b/contribs/gnokeykc/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" + "github.com/zalando/go-keyring" +) + +func main() { + stdio := commands.NewDefaultIO() + wrappedio := &wrappedIO{IO: stdio} + cmd := client.NewRootCmd(wrappedio) + cmd.AddSubCommands(newKcCmd(stdio)) + + if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%+v\n", err) + + os.Exit(1) + } +} + +type wrappedIO struct { + commands.IO +} + +func (io *wrappedIO) GetPassword(prompt string, insecure bool) (string, error) { + return keyring.Get(kcService, kcName) +} diff --git a/contribs/gnomd/go.mod b/contribs/gnomd/go.mod new file mode 100644 index 00000000000..b631040ce94 --- /dev/null +++ b/contribs/gnomd/go.mod @@ -0,0 +1,25 @@ +module github.com/gnolang/gno/contribs/gnomd + +go 1.20 + +require github.com/MichaelMure/go-term-markdown v0.1.4 + +require ( + github.com/MichaelMure/go-term-text v0.3.1 // indirect + github.com/alecthomas/chroma v0.7.1 // indirect + github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect + github.com/disintegration/imaging v1.6.2 // indirect + github.com/dlclark/regexp2 v1.1.6 // indirect + github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75 // indirect + github.com/fatih/color v1.9.0 // indirect + github.com/gomarkdown/markdown v0.0.0-20191123064959-2c17d62f5098 // indirect + github.com/kyokomi/emoji/v2 v2.2.8 // indirect + github.com/lucasb-eyer/go-colorful v1.0.3 // indirect + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.11 // indirect + github.com/mattn/go-runewidth v0.0.12 // indirect + github.com/rivo/uniseg v0.1.0 // indirect + golang.org/x/image v0.0.0-20191206065243-da761ea9ff43 // indirect + golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect + golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect +) diff --git a/contribs/gnomd/go.sum b/contribs/gnomd/go.sum new file mode 100644 index 00000000000..b4ad4f5c9bf --- /dev/null +++ b/contribs/gnomd/go.sum @@ -0,0 +1,70 @@ +github.com/MichaelMure/go-term-markdown v0.1.4 h1:Ir3kBXDUtOX7dEv0EaQV8CNPpH+T7AfTh0eniMOtNcs= +github.com/MichaelMure/go-term-markdown v0.1.4/go.mod h1:EhcA3+pKYnlUsxYKBJ5Sn1cTQmmBMjeNlpV8nRb+JxA= +github.com/MichaelMure/go-term-text v0.3.1 h1:Kw9kZanyZWiCHOYu9v/8pWEgDQ6UVN9/ix2Vd2zzWf0= +github.com/MichaelMure/go-term-text v0.3.1/go.mod h1:QgVjAEDUnRMlzpS6ky5CGblux7ebeiLnuy9dAaFZu8o= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= +github.com/alecthomas/chroma v0.7.1 h1:G1i02OhUbRi2nJxcNkwJaY/J1gHXj9tt72qN6ZouLFQ= +github.com/alecthomas/chroma v0.7.1/go.mod h1:gHw09mkX1Qp80JlYbmN9L3+4R5o6DJJ3GRShh+AICNc= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= +github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg= +github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75 h1:vbix8DDQ/rfatfFr/8cf/sJfIL69i4BcZfjrVOxsMqk= +github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75/go.mod h1:0gZuvTO1ikSA5LtTI6E13LEOdWQNjIo5MTQOvrV0eFg= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/gomarkdown/markdown v0.0.0-20191123064959-2c17d62f5098 h1:Qxs3bNRWe8GTcKMxYOSXm0jx6j0de8XUtb/fsP3GZ0I= +github.com/gomarkdown/markdown v0.0.0-20191123064959-2c17d62f5098/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU= +github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE= +github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= +github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20191206065243-da761ea9ff43 h1:gQ6GUSD102fPgli+Yb4cR/cGaHF7tNBt+GYoRCpGC7s= +golang.org/x/image v0.0.0-20191206065243-da761ea9ff43/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/contribs/gnomd/main.go b/contribs/gnomd/main.go new file mode 100644 index 00000000000..2d3b7266af5 --- /dev/null +++ b/contribs/gnomd/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + + markdown "github.com/MichaelMure/go-term-markdown" +) + +func main() { + // If no arguments are provided, read from stdin + if len(os.Args) <= 1 { + fileContent, err := ioutil.ReadAll(os.Stdin) + checkErr(err) + renderMarkdown("stdin.gno", fileContent) + } + + // Iterate through command-line arguments (file paths) + for _, filePath := range os.Args[1:] { + fileContent, err := ioutil.ReadFile(filePath) + checkErr(err) + renderMarkdown(filePath, fileContent) + } +} + +func renderMarkdown(filePath string, fileContent []byte) { + fmt.Printf("-- %s --\n", filePath) + + result := markdown.Render(string(fileContent), 80, 6) + fmt.Println(string(result)) +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/docs/assets/explanation/packages/pkg-1.gno b/docs/assets/explanation/packages/pkg-1.gno new file mode 100644 index 00000000000..e68d506612a --- /dev/null +++ b/docs/assets/explanation/packages/pkg-1.gno @@ -0,0 +1,6 @@ +func TotalSupply() uint64 +func BalanceOf(account std.Address) uint64 +func Transfer(to std.Address, amount uint64) +func Approve(spender std.Address, amount uint64) +func TransferFrom(from, to std.Address, amount uint64) +func Allowance(owner, spender std.Address) uint64 diff --git a/docs/assets/explanation/packages/pkg-2.gno b/docs/assets/explanation/packages/pkg-2.gno new file mode 100644 index 00000000000..0054cc95e3d --- /dev/null +++ b/docs/assets/explanation/packages/pkg-2.gno @@ -0,0 +1,11 @@ +// functions that work similarly to those of grc20 +func BalanceOf(owner std.Address) (uint64, error) +func Approve(approved std.Address, tid TokenID) error +func TransferFrom(from, to std.Address, tid TokenID) error + +// functions unique to grc721 +func OwnerOf(tid TokenID) (std.Address, error) +func SafeTransferFrom(from, to std.Address, tid TokenID) error +func SetApprovalForAll(operator std.Address, approved bool) error +func GetApproved(tid TokenID) (std.Address, error) +func IsApprovedForAll(owner, operator std.Address) bool diff --git a/docs/assets/explanation/packages/pkg-3.gno b/docs/assets/explanation/packages/pkg-3.gno new file mode 100644 index 00000000000..f1ba5609d6b --- /dev/null +++ b/docs/assets/explanation/packages/pkg-3.gno @@ -0,0 +1,12 @@ +func TestAddress(name string) std.Address { + if len(name) > std.RawAddressSize { + panic("address name cannot be greater than std.AddressSize bytes") + } + addr := std.RawAddress{} + // TODO: use strings.RepeatString or similar. + // NOTE: I miss python's "".Join(). + blanks := "____________________" + copy(addr[:], []byte(blanks)) + copy(addr[:], []byte(name)) + return std.Address(std.EncodeBech32("g", addr)) +} diff --git a/docs/assets/explanation/packages/pkg-4.gno b/docs/assets/explanation/packages/pkg-4.gno new file mode 100644 index 00000000000..edd34b5cc5d --- /dev/null +++ b/docs/assets/explanation/packages/pkg-4.gno @@ -0,0 +1,8 @@ +admin := users.AddressOrName("g1tntwtvzrkt2gex69f0pttan0fp05zmeg5yykv8") +test2 := users.AddressOrName(testutils.TestAddress("test2")) +recv := users.AddressOrName(testutils.TestAddress("recv")) +normal := users.AddressOrName(testutils.TestAddress("normal")) +owner := users.AddressOrName(testutils.TestAddress("owner")) +spender := users.AddressOrName(testutils.TestAddress("spender")) +recv2 := users.AddressOrName(testutils.TestAddress("recv2")) +mibu := users.AddressOrName(testutils.TestAddress("mint_burn")) diff --git a/docs/assets/getting-started/browsing-gno-source-code/gnoweb-avl.png b/docs/assets/getting-started/browsing-gno-source-code/gnoweb-avl.png new file mode 100644 index 00000000000..3754ccfe88e Binary files /dev/null and b/docs/assets/getting-started/browsing-gno-source-code/gnoweb-avl.png differ diff --git a/docs/assets/getting-started/browsing-gno-source-code/gnoweb-boards-source.png b/docs/assets/getting-started/browsing-gno-source-code/gnoweb-boards-source.png new file mode 100644 index 00000000000..65e0f880bc9 Binary files /dev/null and b/docs/assets/getting-started/browsing-gno-source-code/gnoweb-boards-source.png differ diff --git a/docs/assets/getting-started/browsing-gno-source-code/gnoweb-boards.png b/docs/assets/getting-started/browsing-gno-source-code/gnoweb-boards.png new file mode 100644 index 00000000000..dca226c3ec9 Binary files /dev/null and b/docs/assets/getting-started/browsing-gno-source-code/gnoweb-boards.png differ diff --git a/docs/assets/getting-started/browsing-gno-source-code/gnoweb.png b/docs/assets/getting-started/browsing-gno-source-code/gnoweb.png new file mode 100644 index 00000000000..3ba9035889d Binary files /dev/null and b/docs/assets/getting-started/browsing-gno-source-code/gnoweb.png differ diff --git a/docs/assets/getting-started/creating-a-key-pair/gnokey-add-mnemonic.gif b/docs/assets/getting-started/creating-a-key-pair/gnokey-add-mnemonic.gif new file mode 100644 index 00000000000..641acc9f825 Binary files /dev/null and b/docs/assets/getting-started/creating-a-key-pair/gnokey-add-mnemonic.gif differ diff --git a/docs/assets/getting-started/creating-a-key-pair/gnokey-add-random.gif b/docs/assets/getting-started/creating-a-key-pair/gnokey-add-random.gif new file mode 100644 index 00000000000..dba61a6b287 Binary files /dev/null and b/docs/assets/getting-started/creating-a-key-pair/gnokey-add-random.gif differ diff --git a/docs/assets/getting-started/creating-a-key-pair/gnokey-export.gif b/docs/assets/getting-started/creating-a-key-pair/gnokey-export.gif new file mode 100644 index 00000000000..e56938866f9 Binary files /dev/null and b/docs/assets/getting-started/creating-a-key-pair/gnokey-export.gif differ diff --git a/docs/assets/getting-started/creating-a-key-pair/gnokey-generate.gif b/docs/assets/getting-started/creating-a-key-pair/gnokey-generate.gif new file mode 100644 index 00000000000..ce371487c6c Binary files /dev/null and b/docs/assets/getting-started/creating-a-key-pair/gnokey-generate.gif differ diff --git a/docs/assets/getting-started/creating-a-key-pair/gnokey-import.gif b/docs/assets/getting-started/creating-a-key-pair/gnokey-import.gif new file mode 100644 index 00000000000..8ec80519372 Binary files /dev/null and b/docs/assets/getting-started/creating-a-key-pair/gnokey-import.gif differ diff --git a/docs/assets/getting-started/creating-a-key-pair/gnokey-list.gif b/docs/assets/getting-started/creating-a-key-pair/gnokey-list.gif new file mode 100644 index 00000000000..ad050da585c Binary files /dev/null and b/docs/assets/getting-started/creating-a-key-pair/gnokey-list.gif differ diff --git a/docs/assets/getting-started/local-setup/gno-help.gif b/docs/assets/getting-started/local-setup/gno-help.gif new file mode 100644 index 00000000000..ebd7d4cfcb0 Binary files /dev/null and b/docs/assets/getting-started/local-setup/gno-help.gif differ diff --git a/docs/assets/getting-started/local-setup/gnokey-help.gif b/docs/assets/getting-started/local-setup/gnokey-help.gif new file mode 100644 index 00000000000..d96d2424679 Binary files /dev/null and b/docs/assets/getting-started/local-setup/gnokey-help.gif differ diff --git a/docs/assets/getting-started/local-setup/make-build-gnoland.gif b/docs/assets/getting-started/local-setup/make-build-gnoland.gif new file mode 100644 index 00000000000..fe12670be2c Binary files /dev/null and b/docs/assets/getting-started/local-setup/make-build-gnoland.gif differ diff --git a/docs/assets/getting-started/local-setup/make-build-gnovm.gif b/docs/assets/getting-started/local-setup/make-build-gnovm.gif new file mode 100644 index 00000000000..54f133796d6 Binary files /dev/null and b/docs/assets/getting-started/local-setup/make-build-gnovm.gif differ diff --git a/docs/assets/getting-started/setting-up-a-local-chain/gnoland-start.gif b/docs/assets/getting-started/setting-up-a-local-chain/gnoland-start.gif new file mode 100644 index 00000000000..4da21bc863b Binary files /dev/null and b/docs/assets/getting-started/setting-up-a-local-chain/gnoland-start.gif differ diff --git a/docs/assets/getting-started/setting-up-funds/faucet-page.png b/docs/assets/getting-started/setting-up-funds/faucet-page.png new file mode 100644 index 00000000000..f1a5420659f Binary files /dev/null and b/docs/assets/getting-started/setting-up-funds/faucet-page.png differ diff --git a/docs/assets/getting-started/setting-up-funds/gnofaucet-serve.gif b/docs/assets/getting-started/setting-up-funds/gnofaucet-serve.gif new file mode 100644 index 00000000000..79f46e7563c Binary files /dev/null and b/docs/assets/getting-started/setting-up-funds/gnofaucet-serve.gif differ diff --git a/docs/assets/getting-started/setting-up-funds/gnokey-query.gif b/docs/assets/getting-started/setting-up-funds/gnokey-query.gif new file mode 100644 index 00000000000..9f58c638624 Binary files /dev/null and b/docs/assets/getting-started/setting-up-funds/gnokey-query.gif differ diff --git a/docs/assets/getting-started/setting-up-funds/gnoland-start.gif b/docs/assets/getting-started/setting-up-funds/gnoland-start.gif new file mode 100644 index 00000000000..3c323c40410 Binary files /dev/null and b/docs/assets/getting-started/setting-up-funds/gnoland-start.gif differ diff --git a/docs/assets/getting-started/setting-up-funds/gnoweb.gif b/docs/assets/getting-started/setting-up-funds/gnoweb.gif new file mode 100644 index 00000000000..ce4202dcca4 Binary files /dev/null and b/docs/assets/getting-started/setting-up-funds/gnoweb.gif differ diff --git a/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno b/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno new file mode 100644 index 00000000000..5500f019886 --- /dev/null +++ b/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno @@ -0,0 +1,21 @@ +package mytoken + +import ( + "std" + + "gno.land/p/demo/grc/grc20" +) + +var ( + mytoken *grc20.AdminToken + admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // set admin account +) + +// init is a constructor function that runs only once (at time of deployment) +func init() { + // provision the token's name, symbol and number of decimals + mytoken = grc20.NewAdminToken("Mytoken", "MTKN", 4) + + // set the total supply + mytoken.Mint(admin, 1000000*10000) // @administrator (supply = 1 million) +} diff --git a/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno b/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno new file mode 100644 index 00000000000..25ff85c55ab --- /dev/null +++ b/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno @@ -0,0 +1,83 @@ +func TotalSupply() uint64 { + return mytoken.TotalSupply() +} + +func BalanceOf(owner users.AddressOrName) uint64 { + balance, err := mytoken.BalanceOf(owner.Resolve()) + if err != nil { + panic(err) + } + return balance +} + +func Allowance(owner, spender users.AddressOrName) uint64 { + allowance, err := mytoken.Allowance(owner.Resolve(), spender.Resolve()) + if err != nil { + panic(err) + } + return allowance +} + +func Transfer(to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := mytoken.Transfer(caller, to.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Approve(spender users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := mytoken.Approve(caller, spender.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func TransferFrom(from, to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := mytoken.TransferFrom(caller, from.Resolve(), to.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Mint(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mytoken.Mint(address.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Burn(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mytoken.Burn(address.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return mytoken.RenderHome() + case c == 2 && parts[0] == "balance": + owner := users.AddressOrName(parts[1]) + balance, _ := mytoken.BalanceOf(owner.Resolve()) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} diff --git a/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-1.gno b/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-1.gno new file mode 100644 index 00000000000..14e25bd7264 --- /dev/null +++ b/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-1.gno @@ -0,0 +1,17 @@ +package mynonfungibletoken + +import ( + "std" + + "gno.land/p/demo/grc/grc721" +) + +var ( + admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // set admin account + // provision the token's name and symbol + mynonfungibletoken = grc721.NewBasicNFT("mynonfungibletoken", "MNFT") +) + +func init() { + mintNNFT(admin, 10) // @administrator (supply = 10) +} diff --git a/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-2.gno b/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-2.gno new file mode 100644 index 00000000000..8092596ae0a --- /dev/null +++ b/docs/assets/how-to-guides/creating-grc721/mynonfungibletoken-2.gno @@ -0,0 +1,102 @@ +func mintNNFT(owner std.Address, n uint64) { + count := my.TokenCount() + for i := count; i < count+n; i++ { + tid := grc721.TokenID(ufmt.Sprintf("%d", i)) + mynonfungibletoken.Mint(owner, tid) + } +} + +// Getters + +func BalanceOf(user users.AddressOrName) uint64 { + balance, err := mynonfungibletoken.BalanceOf(user.Resolve()) + if err != nil { + panic(err) + } + + return balance +} + +func OwnerOf(tid grc721.TokenID) std.Address { + owner, err := mynonfungibletoken.OwnerOf(tid) + if err != nil { + panic(err) + } + + return owner +} + +func IsApprovedForAll(owner, user users.AddressOrName) bool { + return mynonfungibletoken.IsApprovedForAll(owner.Resolve(), user.Resolve()) +} + +func GetApproved(tid grc721.TokenID) std.Address { + addr, err := mynonfungibletoken.GetApproved(tid) + if err != nil { + panic(err) + } + + return addr +} + +// Setters + +func Approve(user users.AddressOrName, tid grc721.TokenID) { + err := mynonfungibletoken.Approve(user.Resolve(), tid) + if err != nil { + panic(err) + } +} + +func SetApprovalForAll(user users.AddressOrName, approved bool) { + err := mynonfungibletoken.SetApprovalForAll(user.Resolve(), approved) + if err != nil { + panic(err) + } +} + +func TransferFrom(from, to users.AddressOrName, tid grc721.TokenID) { + err := mynonfungibletoken.TransferFrom(from.Resolve(), to.Resolve(), tid) + if err != nil { + panic(err) + } +} + +// Admin + +func Mint(to users.AddressOrName, tid grc721.TokenID) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mynonfungibletoken.Mint(to.Resolve(), tid) + if err != nil { + panic(err) + } +} + +func Burn(tid grc721.TokenID) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mynonfungibletoken.Burn(tid) + if err != nil { + panic(err) + } +} + +// Render + +func Render(path string) string { + switch { + case path == "": + return mynonfungibletoken.RenderHome() + default: + return "404\n" + } +} + +// Util + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-1.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-1.gno new file mode 100644 index 00000000000..1e1dee77a86 --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-1.gno @@ -0,0 +1,39 @@ +func shouldEqual(t *testing.T, got interface{}, expected interface{}) { + t.Helper() + + if got != expected { + t.Errorf("expected %v(%T), got %v(%T)", expected, expected, got, got) + } +} + +func shouldErr(t *testing.T, err error) { + t.Helper() + if err == nil { + t.Errorf("expected an error, but got nil.") + } +} + +func shouldNoErr(t *testing.T, err error) { + t.Helper() + if err != nil { + t.Errorf("expected no error, but got err: %s.", err.Error()) + } +} + +func shouldPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("should have panic") + } + }() + f() +} + +func shouldNoPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r != nil { + t.Errorf("should not have panic") + } + }() + f() +} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-10.sol b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-10.sol new file mode 100644 index 00000000000..8d7aff1794b --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-10.sol @@ -0,0 +1,29 @@ +/// End the auction and send the highest bid +/// to the beneficiary. +function auctionEnd() external { + // It is a good guideline to structure functions that interact + // with other contracts (i.e. they call functions or send Ether) + // into three phases: + // 1. checking conditions + // 2. performing actions (potentially changing conditions) + // 3. interacting with other contracts + // If these phases are mixed up, the other contract could call + // back into the current contract and modify the state or cause + // effects (ether payout) to be performed multiple times. + // If functions called internally include interaction with external + // contracts, they also have to be considered interaction with + // external contracts. + + // 1. Conditions + if (block.timestamp < auctionEndTime) + revert AuctionNotYetEnded(); + if (ended) + revert AuctionEndAlreadyCalled(); + + // 2. Effects + ended = true; + emit AuctionEnded(highestBidder, highestBid); + + // 3. Interaction + beneficiary.transfer(highestBid); +} \ No newline at end of file diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno new file mode 100644 index 00000000000..e48ebf919a0 --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno @@ -0,0 +1,17 @@ +func AuctionEnd() { + if std.GetHeight() < auctionEndBlock { + panic("Auction hasn't ended") + } + + if ended { + panic("Auction has ended") + + } + ended = true + + // Send the highest bid to the recipient + banker := std.GetBanker(std.BankerTypeRealmSend) + pkgAddr := std.GetOrigPkgAddr() + + banker.SendCoins(pkgAddr, receiver, std.Coins{{"ugnot", int64(highestBid)}}) +} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-12.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-12.gno new file mode 100644 index 00000000000..55817537298 --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-12.gno @@ -0,0 +1,18 @@ +// AuctionEnd() Function Test +func TestAuctionEnd(t *testing.T) { + // Auction is ongoing + shouldPanic(t, AuctionEnd) + + // Auction ends + highestBid = 3 + std.TestSkipHeights(500) + shouldNoPanic(t, AuctionEnd) + shouldEqual(t, ended, true) + + banker := std.GetBanker(std.BankerTypeRealmSend) + shouldEqual(t, banker.GetCoins(receiver).String(), "3ugnot") + + // Auction has already ended + shouldPanic(t, AuctionEnd) + shouldEqual(t, ended, true) +} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno new file mode 100644 index 00000000000..0e5f2d57de9 --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno @@ -0,0 +1,74 @@ +// The whole test +func TestFull(t *testing.T) { + bidder01 := testutils.TestAddress("bidder01") // g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw + bidder02 := testutils.TestAddress("bidder02") // g1vf5kger9wgcryh6lta047h6lta047h6lnhe2x2 + + // Variables test + { + shouldEqual(t, highestBidder, "") + shouldEqual(t, receiver, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + shouldEqual(t, auctionEndBlock, 423) + shouldEqual(t, highestBid, 0) + shouldEqual(t, pendingReturns.Size(), 0) + shouldEqual(t, ended, false) + } + + // Send two or more types of coins + { + std.TestSetOrigCaller(bidder01) + std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) + shouldPanic(t, Bid) + } + + // Send less than the highest bid + { + std.TestSetOrigCaller(bidder01) + std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) + shouldPanic(t, Bid) + } + + // Send more than the highest bid + { + std.TestSetOrigCaller(bidder01) + std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + shouldNoPanic(t, Bid) + + shouldEqual(t, pendingReturns.Size(), 0) + shouldEqual(t, highestBid, 1) + shouldEqual(t, highestBidder, "g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw") + } + + // Other participants in the auction + { + + // Send less amount than the current highest bid (current: 1) + std.TestSetOrigCaller(bidder02) + std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + shouldPanic(t, Bid) + + // Send more amount than the current highest bid (exceeded) + std.TestSetOrigCaller(bidder02) + std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) + shouldNoPanic(t, Bid) + + shouldEqual(t, highestBid, 2) + shouldEqual(t, highestBidder, "g1vf5kger9wgcryh6lta047h6lta047h6lnhe2x2") + + shouldEqual(t, pendingReturns.Size(), 1) // Return to the existing bidder + shouldEqual(t, pendingReturns.Has("g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw"), true) + } + + // Auction ends + { + std.TestSkipHeights(150) + shouldPanic(t, AuctionEnd) + shouldEqual(t, ended, false) + + std.TestSkipHeights(301) + shouldNoPanic(t, AuctionEnd) + shouldEqual(t, ended, true) + + banker := std.GetBanker(std.BankerTypeRealmSend) + shouldEqual(t, banker.GetCoins(receiver).String(), "2ugnot") + } +} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-2.sol b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-2.sol new file mode 100644 index 00000000000..0040c3ca75f --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-2.sol @@ -0,0 +1,47 @@ +// Parameters of the auction. Times are either +// absolute unix timestamps (seconds since 1970-01-01) +// or time periods in seconds. +address payable public beneficiary; +uint public auctionEndTime; + +// Current state of the auction. +address public highestBidder; +uint public highestBid; + +// Allowed withdrawals of previous bids +mapping(address => uint) pendingReturns; + +// Set to true at the end, disallows any change. +// By default initialized to `false`. +bool ended; + +// Events that will be emitted on changes. +event HighestBidIncreased(address bidder, uint amount); +event AuctionEnded(address winner, uint amount); + +// Errors that describe failures. + +// The triple-slash comments are so-called natspec +// comments. They will be shown when the user +// is asked to confirm a transaction or +// when an error is displayed. + +/// The auction has already ended. +error AuctionAlreadyEnded(); +/// There is already a higher or equal bid. +error BidNotHighEnough(uint highestBid); +/// The auction has not ended yet. +error AuctionNotYetEnded(); +/// The function auctionEnd has already been called. +error AuctionEndAlreadyCalled(); + +/// Create a simple auction with `biddingTime` +/// seconds bidding time on behalf of the +/// beneficiary address `beneficiaryAddress`. +constructor( + uint biddingTime, + address payable beneficiaryAddress +) { + beneficiary = beneficiaryAddress; + auctionEndTime = block.timestamp + biddingTime; +} \ No newline at end of file diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-3.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-3.gno new file mode 100644 index 00000000000..af8137b4044 --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-3.gno @@ -0,0 +1,8 @@ +var ( + receiver = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + auctionEndBlock = std.GetHeight() + uint(300) // in blocks + highestBidder std.Address + highestBid = uint(0) + pendingReturns avl.Tree + ended = false +) diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-4.sol b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-4.sol new file mode 100644 index 00000000000..0e3ab6d7e0d --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-4.sol @@ -0,0 +1,32 @@ +function bid() external payable { + // No arguments are necessary, all + // information is already part of + // the transaction. The keyword payable + // is required for the function to + // be able to receive Ether. + + // Revert the call if the bidding + // period is over. + if (block.timestamp > auctionEndTime) + revert AuctionAlreadyEnded(); + + // If the bid is not higher, send the + // money back (the revert statement + // will revert all changes in this + // function execution including + // it having received the money). + if (msg.value <= highestBid) + revert BidNotHighEnough(highestBid); + + if (highestBid != 0) { + // Sending back the money by simply using + // highestBidder.send(highestBid) is a security risk + // because it could execute an untrusted contract. + // It is always safer to let the recipients + // withdraw their money themselves. + pendingReturns[highestBidder] += highestBid; + } + highestBidder = msg.sender; + highestBid = msg.value; + emit HighestBidIncreased(msg.sender, msg.value); +} \ No newline at end of file diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno new file mode 100644 index 00000000000..43f0b43b397 --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno @@ -0,0 +1,30 @@ +func Bid() { + if std.GetHeight() > auctionEndBlock { + panic("Exceeded auction end block") + } + + sentCoins := std.GetOrigSend() + if len(sentCoins) != 1 { + panic("Send only one type of coin") + } + + sentAmount := uint(sentCoins[0].Amount) + if sentAmount <= highestBid { + panic("Too few coins sent") + } + + // A new bid is higher than the current highest bid + if sentAmount > highestBid { + // If the highest bid is greater than 0, + if highestBid > 0 { + // Need to return the bid amount to the existing highest bidder + // Create an AVL tree and save + pendingReturns.Set(highestBidder.String(), highestBid) + } + + // Update the top bidder address + highestBidder = std.GetOrigCaller() + // Update the top bid amount + highestBid = sentAmount + } +} 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 new file mode 100644 index 00000000000..b544d0017c4 --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno @@ -0,0 +1,41 @@ +// 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) +} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-7.sol b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-7.sol new file mode 100644 index 00000000000..b28ecec1f52 --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-7.sol @@ -0,0 +1,20 @@ +/// Withdraw a bid that was overbid. +function withdraw() external returns (bool) { + uint amount = pendingReturns[msg.sender]; + if (amount > 0) { + // It is important to set this to zero because the recipient + // can call this function again as part of the receiving call + // before `send` returns. + pendingReturns[msg.sender] = 0; + + // msg.sender is not of type `address payable` and must be + // explicitly converted using `payable(msg.sender)` in order + // use the member function `send()`. + if (!payable(msg.sender).send(amount)) { + // No need to call throw here, just reset the amount owing + pendingReturns[msg.sender] = amount; + return false; + } + } + return true; +} \ No newline at end of file diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno new file mode 100644 index 00000000000..7cb6bbd8d90 --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno @@ -0,0 +1,15 @@ +func Withdraw() { + // Query the return amount to non-highest bidders + amount, _ := pendingReturns.Get(std.GetOrigCaller().String()) + + if amount > 0 { + // If there's an amount, reset the amount first, + pendingReturns.Set(std.GetOrigCaller().String(), 0) + + // Return the exceeded amount + banker := std.GetBanker(std.BankerTypeRealmSend) + pkgAddr := std.GetOrigPkgAddr() + + banker.SendCoins(pkgAddr, std.GetOrigCaller(), std.Coins{{"ugnot", amount.(int64)}}) + } +} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno new file mode 100644 index 00000000000..fbc06792ce4 --- /dev/null +++ b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno @@ -0,0 +1,17 @@ +// Withdraw Function Test +func TestWithdraw(t *testing.T) { + // If there's no participants for return + shouldEqual(t, pendingReturns.Size(), 0) + + // If there's participants for return (data generation + returnAddr := bidder01.String() + returnAmount := int64(3) + pendingReturns.Set(returnAddr, returnAmount) + shouldEqual(t, pendingReturns.Size(), 1) + shouldEqual(t, pendingReturns.Has(returnAddr), true) + + banker := std.GetBanker(std.BankerTypeRealmSend) + pkgAddr := std.GetOrigPkgAddr() + banker.SendCoins(pkgAddr, std.Address(returnAddr), std.Coins{{"ugnot", returnAmount}}) + shouldEqual(t, banker.GetCoins(std.Address(returnAddr)).String(), "3ugnot") +} diff --git a/docs/assets/how-to-guides/simple-contract/counter.gno b/docs/assets/how-to-guides/simple-contract/counter.gno new file mode 100644 index 00000000000..cf17b0c6fdc --- /dev/null +++ b/docs/assets/how-to-guides/simple-contract/counter.gno @@ -0,0 +1,17 @@ +package counter + +import "fmt" + +var count int + +func Increment() { + count++ +} + +func Decrement() { + count-- +} + +func Render(_ string) string { + return fmt.Sprintf("Count: %d", count) +} diff --git a/docs/assets/how-to-guides/simple-contract/init.gno b/docs/assets/how-to-guides/simple-contract/init.gno new file mode 100644 index 00000000000..823c394dfa0 --- /dev/null +++ b/docs/assets/how-to-guides/simple-contract/init.gno @@ -0,0 +1,11 @@ +package counter + +var count int + +// ... + +func init() { + count = 2 * 10 // arbitrary value +} + +// ... diff --git a/docs/assets/how-to-guides/simple-library/tapas.gno b/docs/assets/how-to-guides/simple-library/tapas.gno new file mode 100644 index 00000000000..7d8eb184898 --- /dev/null +++ b/docs/assets/how-to-guides/simple-library/tapas.gno @@ -0,0 +1,40 @@ +package tapas + +import ( + "gno.land/p/demo/rand" +) + +// List of tapas suggestions +var listOfTapas = []string{ + "Patatas Bravas", + "Gambas al Ajillo", + "Croquetas", + "Tortilla Española", + "Pimientos de Padrón", + "Jamon Serrano", + "Boquerones en Vinagre", + "Calamares a la Romana", + "Pulpo a la Gallega", + "Tostada con Tomate", + "Mejillones en Escabeche", + "Chorizo a la Sidra", + "Cazón en Adobo", + "Banderillas", + "Espárragos a la Parrilla", + "Huevos Rellenos", + "Tuna Empanada", + "Sardinas a la Plancha", +} + +// GetTapaSuggestion randomly selects and returns a tapa suggestion +func GetTapaSuggestion() string { + // Create a new instance of the random number generator. + // Notice that this is from an imported Gno library + generator := rand.New() + + // Generate a random index + randomIndex := generator.Intn(len(listOfTapas)) + + // Return the random suggestion + return listOfTapas[randomIndex] +} diff --git a/docs/assets/how-to-guides/testing-gno/counter-1.gno b/docs/assets/how-to-guides/testing-gno/counter-1.gno new file mode 100644 index 00000000000..1741f677480 --- /dev/null +++ b/docs/assets/how-to-guides/testing-gno/counter-1.gno @@ -0,0 +1,19 @@ +// counter-app/r/counter/counter.gno + +package counter + +import "fmt" + +var count int + +func Increment() { + count++ +} + +func Decrement() { + count-- +} + +func Render(_ string) string { + return fmt.Sprintf("Count: %d", count) +} diff --git a/docs/assets/how-to-guides/testing-gno/counter-2.gno b/docs/assets/how-to-guides/testing-gno/counter-2.gno new file mode 100644 index 00000000000..1298432bc03 --- /dev/null +++ b/docs/assets/how-to-guides/testing-gno/counter-2.gno @@ -0,0 +1,51 @@ +// counter-app/r/counter/counter_test.gno + +package counter + +import "testing" + +func TestCounter_Increment(t *testing.T) { + // Reset the value + count = 0 + + // Verify the initial value is 0 + if count != 0 { + t.Fatalf("initial value != 0") + } + + // Increment the value + Increment() + + // Verify the initial value is 1 + if count != 1 { + t.Fatalf("initial value != 1") + } +} + +func TestCounter_Decrement(t *testing.T) { + // Reset the value + count = 0 + + // Verify the initial value is 0 + if count != 0 { + t.Fatalf("initial value != 0") + } + + // Decrement the value + Decrement() + + // Verify the initial value is 1 + if count != -1 { + t.Fatalf("initial value != -1") + } +} + +func TestCounter_Render(t *testing.T) { + // Reset the value + count = 0 + + // Verify the Render output + if Render("") != "Count: 0" { + t.Fatalf("invalid Render value") + } +} diff --git a/docs/assets/how-to-guides/write-simple-dapp/poll-1.gno b/docs/assets/how-to-guides/write-simple-dapp/poll-1.gno new file mode 100644 index 00000000000..45ac073a387 --- /dev/null +++ b/docs/assets/how-to-guides/write-simple-dapp/poll-1.gno @@ -0,0 +1,69 @@ +package poll + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// Main struct +type Poll struct { + title string + description string + deadline int64 // block height + voters *avl.Tree // addr -> yes / no (bool) +} + +// Getters +func (p Poll) Title() string { + return p.title +} + +func (p Poll) Description() string { + return p.description +} + +func (p Poll) Deadline() int64 { + return p.deadline +} + +func (p Poll) Voters() *avl.Tree { + return p.voters +} + +// Poll instance constructor +func NewPoll(title, description string, deadline int64) *Poll { + return &Poll{ + title: title, + description: description, + deadline: deadline, + voters: avl.NewTree(), + } +} + +// Vote Votes for a user +func (p *Poll) Vote(voter std.Address, vote bool) { + p.Voters().Set(string(voter), vote) +} + +// HasVoted vote: yes - true, no - false +func (p *Poll) HasVoted(address std.Address) (bool, bool) { + vote, exists := p.Voters().Get(string(address)) + if exists { + return true, vote.(bool) + } + return false, false +} + +// VoteCount Returns the number of yay & nay votes +func (p Poll) VoteCount() (int, int) { + var yay int + + p.Voters().Iterate("", "", func(key string, value interface{}) bool { + vote := value.(bool) + if vote == true { + yay = yay + 1 + } + }) + return yay, p.Voters().Size() - yay +} diff --git a/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno b/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno new file mode 100644 index 00000000000..7720c042f98 --- /dev/null +++ b/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno @@ -0,0 +1,78 @@ +package poll + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/poll" + "gno.land/p/demo/ufmt" +) + +// state variables +var ( + polls *avl.Tree // id -> Poll + pollIDCounter int +) + +func init() { + polls = avl.NewTree() + pollIDCounter = 0 +} + +// NewPoll - Creates a new Poll instance +func NewPoll(title, description string, deadline int64) string { + // get block height + if deadline <= std.GetHeight() { + return "Error: Deadline has to be in the future." + } + + // convert int ID to string used in AVL tree + id := ufmt.Sprintf("%d", pollIDCounter) + p := poll.NewPoll(title, description, deadline) + + // add new poll in avl tree + polls.Set(id, p) + + // increment ID counter + pollIDCounter = pollIDCounter + 1 + + return ufmt.Sprintf("Successfully created poll #%s!", id) +} + +// Vote - vote for a specific Poll +// yes - true, no - false +func Vote(pollID int, vote bool) string { + // get txSender + txSender := std.GetOrigCaller() + + id := ufmt.Sprintf("%d", pollID) + // get specific Poll from AVL tree + pollRaw, exists := polls.Get(id) + + if !exists { + return "Error: Poll with specified doesn't exist." + } + + // cast Poll into proper format + poll, _ := pollRaw.(*poll.Poll) + + voted, _ := poll.HasVoted(txSender) + if voted { + return "Error: You've already voted!" + } + + if poll.Deadline() <= std.GetHeight() { + return "Error: Voting for this poll is closed." + } + + // record vote + poll.Vote(txSender, vote) + + // update Poll in tree + polls.Set(id, poll) + + if vote == true { + return ufmt.Sprintf("Successfully voted YAY for poll #%s!", id) + } + return ufmt.Sprintf("Successfully voted NAY for poll #%s!", id) +} diff --git a/docs/assets/how-to-guides/write-simple-dapp/poll-3.gno b/docs/assets/how-to-guides/write-simple-dapp/poll-3.gno new file mode 100644 index 00000000000..281c209c1ff --- /dev/null +++ b/docs/assets/how-to-guides/write-simple-dapp/poll-3.gno @@ -0,0 +1,74 @@ +func Render(path string) string { + var b bytes.Buffer + + b.WriteString("# Polls!\n\n") + + if polls.Size() == 0 { + b.WriteString("### No active polls currently!") + return b.String() + } + polls.Iterate("", "", func(key string, value interface{}) bool { + + // cast raw data from tree into Poll struct + p := value.(*poll.Poll) + ddl := p.Deadline() + + yay, nay := p.VoteCount() + yayPercent := 0 + nayPercent := 0 + + if yay+nay != 0 { + yayPercent = yay * 100 / (yay + nay) + nayPercent = nay * 100 / (yay + nay) + } + + b.WriteString( + ufmt.Sprintf( + "## Poll #%s: %s\n", + key, // poll ID + p.Title(), + ), + ) + + dropdown := "
\nPoll details
" + + b.WriteString(dropdown + "Description: " + p.Description()) + + b.WriteString( + ufmt.Sprintf("
Voting until block: %d
Current vote count: %d", + p.Deadline(), + p.Voters().Size()), + ) + + b.WriteString( + ufmt.Sprintf("
YAY votes: %d (%d%%)", yay, yayPercent), + ) + b.WriteString( + ufmt.Sprintf("
NAY votes: %d (%d%%)
", nay, nayPercent), + ) + + dropdown = "
\nVote details" + b.WriteString(dropdown) + + p.Voters().Iterate("", "", func(key string, value interface{}) bool { + + voter := key + vote := value.(bool) + + if vote == true { + b.WriteString( + ufmt.Sprintf("
%s voted YAY!", voter), + ) + } else { + b.WriteString( + ufmt.Sprintf("
%s voted NAY!", voter), + ) + } + return false + }) + + b.WriteString("
\n\n") + return false + }) + return b.String() +} diff --git a/docs/assets/reference/standard-library/std-1.gno b/docs/assets/reference/standard-library/std-1.gno new file mode 100644 index 00000000000..5f5a5d86b76 --- /dev/null +++ b/docs/assets/reference/standard-library/std-1.gno @@ -0,0 +1,14 @@ +// returns the list of coins owned by the address +GetCoins(addr Address) (dst Coins) + +// sends coins from one address to another +SendCoins(from, to Address, amt Coins) + +// returns the total supply of the coin +TotalCoin(denom string) int64 + +// issues coins to the address +IssueCoin(addr Address, denom string, amount int64) + +// burns coins from the address +RemoveCoin(addr Address, denom string, amount int64) diff --git a/docs/assets/reference/standard-library/std-2.gno b/docs/assets/reference/standard-library/std-2.gno new file mode 100644 index 00000000000..1ef9a0a1af1 --- /dev/null +++ b/docs/assets/reference/standard-library/std-2.gno @@ -0,0 +1,4 @@ +type Coin struct { + Denom string `json:"denom"` // the symbol of the coin + Amount int64 `json:"amount"` // the quantity of the coin +} diff --git a/docs/from-go-to-gno.md b/docs/explanation/from-go-to-gno.md similarity index 97% rename from docs/from-go-to-gno.md rename to docs/explanation/from-go-to-gno.md index fc7f28ab73a..41cccc6e971 100644 --- a/docs/from-go-to-gno.md +++ b/docs/explanation/from-go-to-gno.md @@ -1,3 +1,7 @@ +--- +id: from-go-to-gno +--- + # From Go to Gno ## Runtime comparison @@ -69,5 +73,5 @@ TODO ## See also -- [go-gno-compatibility.md](./go-gno-compatibility.md) +- [go-gno-compatibility.md](../reference/go-gno-compatibility.md) - ["go -> gno" presentation by Zack Scholl](https://github.com/gnolang/workshops/tree/main/presentations/2023-06-26--go-to-gno--schollz) diff --git a/docs/explanation/gno-language.md b/docs/explanation/gno-language.md new file mode 100644 index 00000000000..4e0605a6fac --- /dev/null +++ b/docs/explanation/gno-language.md @@ -0,0 +1,68 @@ +--- +id: gno-language +--- + +# The Gno Language + +Gno (Gnolang) is an interpretation of the widely-used Go (Golang) programming language for blockchain created by Cosmos +co-founder Jae Kwon in 2021 to mark a new era in smart contracting. Gno is almost identical to Go, so Go developers can +quickly start using it, with minimal effort. For example, Gno comes with blockchain-specific standard libraries, but any +code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that could lead to +non-deterministic behaviour when executed by thousands of validators are not available in Gno, such as network access, +or determining system time. Otherwise, Gno loads and uses many standard libraries that power Go, so the experience +writing code feels very similar to Go's. + +Under the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, +rather than bytecode as in many virtual machines such as Java, Python, or Wasm. The design aims to make reading & +understanding the source code of the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM +interpreter allows Gno to freeze and resume the program by persisting and loading the memory state automatically. Gno is +deterministic, auto-persisted, and auto-Merkle-ized, allowing programs to be succinct, as the programmer doesn’t have to +serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK). + +## How Gno Differs from Go + +The composable nature of Go/Gno allows for type-checked interactions between contracts, making Gno.land safer and more +powerful, as well as operationally cheaper and faster. Smart contracts on Gno.land are light, simple, more focused, and +easily interoperable - they represent a network of interconnected contracts rather than siloed monoliths that limit +interactions with other contracts. + +## Gno Inherits Go’s Built-in Security Features + +Go supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to +create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by +the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple +users. + +Another major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and +third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart +contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping +developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common +mistakes. + +## Gno vs Solidity + +The most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from +the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) +and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling +and its EVM has a stack limit and computational inefficiencies. + +Solidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In +addition, developers have to learn several languages if they want to understand the whole stack or work across different +ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or +Substrate) as every part of the stack is written in Go (or Gno!). It’s easy for developers to understand the entire system just +by studying a relatively small code base. + +## Gno Is Essential for the Wider Adoption of Web3 + +Gno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and +support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, +enabling remote function calls as local, and providing automatic and independent per-realm state persistence. + +Using Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and +reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive +way while preserving localism, and the language specification is simple, successfully balancing practicality and +minimalism. + +Building on top of the excellent design of Go, the aim for Gno programming is to become the new gold standard for smart +contract development, not just in our ecosystem but blockchain as a whole. Combining Go's large success, together with +type safety and composability, Gno aims to kickstart a broader adoption of Web3 and its growth. diff --git a/docs/explanation/gno-modules.md b/docs/explanation/gno-modules.md new file mode 100644 index 00000000000..30f1de7b714 --- /dev/null +++ b/docs/explanation/gno-modules.md @@ -0,0 +1,42 @@ +--- +id: gno-modules +--- + +# Gno Modules + +The packages and realms containing `gno.mod` file can be reffered as Gno modules. `gno.mod` file is introduced to enhance local testing and handle dependency management while testing Gno packages/realms locally. At the time of writing, `gno.mod` is only used by the `gno` tool for local development, and it is disregarded on the Gno.land chain. + +## What is the gno.mod file for? + +`gno.mod` file is very useful for local testing and development. Its primary purposes include: + +- **Working outside of the monorepo**: by adding a `gno.mod` file to your directory, all gno tooling will recognise it and understand the implicit import path of your current directory (marked by the `module` directive in your `gno.mod` file). +- **Local dependency management**: the gno.mod file allows you to manage and download local dependencies effectively when developing Go Modules. +- **Configuration and metadata (WIP)**: while the gno.mod file is currently used for specifying dependencies, it's worth noting that in the future, it might also serve as a container for additional configuration and metadata related to Gno Modules. For more information, see: [issue #498](https://github.com/gnolang/gno/issues/498). + +## Gno Modules and Subdirectories + +It's important to note that Gno Modules do not include subdirectories. Each directory within your project is treated as an individual Gno Module, and each should contain its own gno.mod file, even if it's located within an existing Gno Module directory. + +## Available gno Commands + +The gno command-line tool provides several commands to work with the gno.mod file and manage dependencies in Gno Modules: + +- **gno mod init**: small helper to initialize a new `gno.mod` file. +- **gno mod download**: downloads the dependencies specified in the gno.mod file. This command fetches the required dependencies from chain and ensures they are available for local testing and development. +- **gno mod tidy**: removes any unused dependency and adds any required but not yet listed in the file -- most of the maintenance you'll usually need to do! + +## Sample `gno.mod` file + +``` +module gno.land/p/demo/sample + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest +) + +``` + +- **`module gno.land/p/demo/sample`**: specifies the package/realm import path. +- **`require` Block**: lists the required dependencies. Here using the latest available versions of "gno.land/p/demo/avl" and "gno.land/p/demo/testutils". These dependencies should be specified with the version "v0.0.0-latest" since on-chain packages currently do not support versioning. diff --git a/docs/explanation/gno-test.md b/docs/explanation/gno-test.md new file mode 100644 index 00000000000..cd585298b9d --- /dev/null +++ b/docs/explanation/gno-test.md @@ -0,0 +1,82 @@ +--- +id: gno-test +--- + +# Gno Test + +There are two methods for testing a realm or package during the development phase: + +1. Calling the realm/package after deploying it on a local network (or testnet). +2. Using the `test` option within the [`gno`](./gno-tooling/cli/gno.md) CLI. + +While the first method is recommended for its accuracy and similarity to the actual deployment environment, it is more efficient to initially utilize the second method for composing test cases and then proceed to the first method if no errors are detected. + +This section will teach you how to use the second method. + +Writing test cases in Gnolang is similar to that of Golang, with general rules as the following: + +* Test file naming conventions must be adhered to (ex: `xxx_test.gno`). +* Test functions must start with `Test`. +* The `t *testing.T` argument must be included in each test function. + * The `testing` package must be imported. +* Tests must be run with the `gno test` command. + +Let's write a sample code and test it. + +```go +// contract.gno + +package demo + +func Hello(name string) string { + return "Hello " + name + "!" +} +``` + +This is a simple code that returns the string-typed argument in a specific format. + +Next, we'll write a test case that looks like the following: + +```go +// contract_test.gno + +package demo + +import "testing" + +func Test(t *testing.T) { + { + got := Hello("People") + expected := "Hello People!" + if got != expected { + t.Fatalf("expected %q, got %q.", expected, got) + } + } + { + got := Hello("") + expected := "Hello People!" + if got != expected { + t.Fatalf("expected %q, got %q.", expected, got) + } + } +} +``` + +Two conditions exist in the test case above. + +1. "Hello People!" should be returned when calling `Hello("People")`. +2. "Hello People!" should be returned when calling `Hello("")`. + +Upon examination of our realm code and the associated test results, the initial condition exhibited the desired behavior; however, an error was identified in the second condition. Despite the expected outcome of "Hello" being returned, the test case incorrectly specified that the expected output should be "Hello People!" instead. + +Replacing the second test case with the following will successfully fix the issue and allow the test to pass. + +```go + { + got := Hello("") + expected := "Hello !" + if expected != got { + t.Fatalf("expected %q, got %q.", expected, got) + } + } +``` diff --git a/docs/explanation/gno-tooling/cli/gno.md b/docs/explanation/gno-tooling/cli/gno.md new file mode 100644 index 00000000000..3141476f02a --- /dev/null +++ b/docs/explanation/gno-tooling/cli/gno.md @@ -0,0 +1,66 @@ +--- +id: gno-tooling-gno +--- + +# gno + +`gno` is a handy tool for developing and prototyping Gno packages and realms. You may use `gno` to use the GnoVM without an actual blockchain to build or test realms in a local environment. + +## Run `gno` Commands + +The following command will run `gno`. + +```bash +gno {SUB_COMMAND} +``` + +**Subcommands** + +| Name | Description | +| ------------ | ------------------------------------------ | +| `build` | Builds a gno package. | +| `test` | Tests a gno package. | +| `precompile` | Precompiles a `.gno` file to a `.go` file. | +| `repl` | Starts a GnoVM REPL. | + +### `build` + +#### **Options** + +| Name | Type | Description | +| --------- | ------- | ---------------------------------------------- | +| `verbose` | Boolean | Displays extended information. | +| go-binary | String | Go binary to use for building (default: `go`). | + +### `test` + +#### **Options** + +| Name | Type | Description | +| ------------ | ------------- | ------------------------------------------------------------------ | +| `verbose` | Boolean | Displays extended information. | +| `root-dir` | String | Clones location of github.com/gnolang/gno (gno tries to guess it). | +| `run` | String | Test name filtering pattern. | +| `timeout` | time.Duration | The maximum execution time in ns. | +| `precompile` | Boolean | Precompiles a `.gno` file to a `.go` file before testing. | + +### `precompile` + +#### **Options** + +| Name | Type | Description | +| ----------- | ------- | --------------------------------------------------------------- | +| `verbose` | Boolean | Displays extended information. | +| `skip-fmt` | Boolean | Skips the syntax checking of generated `.go` files. | +| `go-binary` | String | The go binary to use for building (default: `go`). | +| `go-binary` | String | The gofmt binary to use for syntax checking (default: `gofmt`). | +| `output` | String | The output directory (default: `.`). | + +### `repl` + +#### **Options** + +| Name | Type | Description | +| ---------- | ------- | ------------------------------------------------------------------ | +| `verbose` | Boolean | Displays extended information. | +| `root-dir` | String | Clones location of github.com/gnolang/gno (gno tries to guess it). | diff --git a/docs/explanation/gno-tooling/cli/gnofaucet.md b/docs/explanation/gno-tooling/cli/gnofaucet.md new file mode 100644 index 00000000000..3e70add89c0 --- /dev/null +++ b/docs/explanation/gno-tooling/cli/gnofaucet.md @@ -0,0 +1,61 @@ +--- +id: gno-tooling-gnofaucet +--- + +# gnofaucet + +`gnofaucet` is a server for distributing GNOT, the gas currency of Gnoland, to specific addresses in a local chain. +Interact with the `gnofaucet` from an address with an empty balance in your locally built testnet to fuel it with GNOT +to pay for transactions. + +## Run `gnofaucet` Commands + +Enable the faucet using the following command. + +```bash +gnofaucet serve +``` + +#### **Options** + +| Name | Type | Description | +|---------------------------|---------|--------------------------------------------------------------------------------------| +| `chain-id` | String | The id of the chain (required). | +| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction (default: `50000`) | +| `gas-fee` | String | The gas fee to pay for the transaction. | +| `memo` | String | Any descriptive text (default: `""`) | +| `test-to` | String | Test address (optional) | +| `send` | String | Coins to send (default: `"1000000ugnot"`). | +| `captcha-secret` | String | The secret key for the recaptcha. If empty, the captcha is disabled (default: `""`). | +| `is-behind-proxy` | Boolean | Uses X-Forwarded-For IP for throttling (default: `false`). | +| `insecure-password-stdin` | Boolean | INSECURE! Takes password from stdin (default: `false`). | + +## Example + +### Step 1. Create an account named `test1` with the test seed phrase below. + +```bash +gnokey add test1 --recover +``` + +> **Test Seed Phrase:** source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate +> oppose farm nothing bullet exhibit title speed wink action roast + +### **Step 2. Run `gnofaucet`** + +```bash +gnofaucet serve test1 --chain-id dev --send 500000000ugnot +``` + +### **Step 3. Receive GNOTs from the faucet** + +To receive funds through the `gnoweb` form GUI, you can request them on: +`http://localhost:8888/faucet` (given `http://localhost:8888/` is the location where `gnoweb` is serving pages). + +Alternatively, you can request funds from the faucet by directly invoking a CURL command: + +```bash +curl --location --request POST 'http://localhost:5050' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'toaddr={address to receive}' +``` diff --git a/docs/explanation/gno-tooling/cli/gnokey.md b/docs/explanation/gno-tooling/cli/gnokey.md new file mode 100644 index 00000000000..dffee356509 --- /dev/null +++ b/docs/explanation/gno-tooling/cli/gnokey.md @@ -0,0 +1,290 @@ +--- +id: gno-tooling-gnokey +--- + +# gnokey + +Used for account & key management and general interactions with the Gnoland blockchain. + +## Generate a New Seed Phrase + +Generate a new seed phrase and add it to your keybase with the following command. + +```bash +gnokey generate +``` + +## Add a New Key + +You can add a new private key to the keybase using the following command. + +```bash +gnokey add {KEY_NAME} +``` + +#### **Options** + +| Name | Type | Description | +| ----------- | ---------- | -------------------------------------------------------------------------------------- | +| `account` | UInt | Account number for HD derivation. | +| `dryrun` | Boolean | Performs action, but doesn't add key to local keystore. | +| `index` | UInt | Address index number for HD derivation. | +| `ledger` | Boolean | Stores a local reference to a private key on a Ledger device. | +| `multisig` | String \[] | Constructs and stores a multisig public key (implies `--pubkey`). | +| `nobackup` | Boolean | Doesn't print out seed phrase (if others are watching the terminal). | +| `nosort` | Boolean | Keys passed to `--multisig` are taken in the order they're supplied. | +| `pubkey` | String | Parses a public key in bech32 format and save it to disk. | +| `recover` | Boolean | Provides seed phrase to recover existing key instead of creating. | +| `threshold` | Int | K out of N required signatures. For use in conjunction with --multisig (default: `1`). | + +> **Test Seed Phrase:** source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast + +## List all Known Keys + +List all keys stored in your keybase with the following command. + +```bash +gnokey list +``` + +## Delete a Key + +Delete a key from your keybase with the following command. + +```bash +gnokey delete {KEY_NAME} +``` + +#### **Options** + +| Name | Type | Description | +| ------- | ------- | ---------------------------- | +| `yes` | Boolean | Skips confirmation prompt. | +| `force` | Boolean | Removes key unconditionally. | + + +## Export a Private Key (Encrypted & Unencrypted) + +Export a private key's (encrypted or unencrypted) armor using the following command. + +```bash +gnokey export +``` + +#### **Options** + +| Name | Type | Description | +| ------------- | ------ | ------------------------------------------- | +| `key` | String | Name or Bech32 address of the private key | +| `output-path` | String | The desired output path for the armor file | +| `unsafe` | Bool | Export the private key armor as unencrypted | + + +## Import a Private Key (Encrypted & Unencrypted) + +Import a private key's (encrypted or unencrypted) armor with the following command. + +```bash +gnokey import +``` + +#### **Options** + +| Name | Type | Description | +| ------------ | ------ | ------------------------------------------- | +| `armor-path` | String | The path to the encrypted armor file. | +| `name` | String | The name of the private key. | +| `unsafe` | Bool | Import the private key armor as unencrypted | + + +## Make an ABCI Query + +Make an ABCI Query with the following command. + +```bash +gnokey query {QUERY_PATH} +``` + +#### **Query** + +| Query Path | Description | Example | +| ------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | +| `auth/accounts/{ADDRESS}` | Returns information about an account. | `gnokey query auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` | +| `bank/balances/{ADDRESS}` | Returns balances of an account. | `gnokey query bank/balances/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` | +| `vm/qfuncs` | Returns public facing function signatures as JSON. | `gnokey query vm/qfuncs --data "gno.land/r/demo/boards"` | +| `vm/qfile` | Returns the file bytes, or list of files if directory. | `gnokey query vm/qfile --data "gno.land/r/demo/boards"` | +| `vm/qrender` | Calls .Render(path) in readonly mode. | `gnokey query vm/qrender --data "gno.land/r/demo/boards"` | +| `vm/qeval` | Evaluates any expression in readonly mode and returns the results. | `gnokey query vm/qeval --data "gno.land/r/demo/boards GetBoardIDFromName("my_board")"` | +| `vm/store` | (not yet supported) Fetches items from the store. | - | +| `vm/package` | (not yet supported) Fetches a package's files. | - | + +#### **Options** + +| Name | Type | Description | +| -------- | --------- | ---------------------------------------- | +| `data` | UInt8 \[] | Queries data bytes. | +| `height` | Int64 | (not yet supported) Queries height. | +| `prove` | Boolean | (not yet supported) Proves query result. | + + +## Sign and Broadcast a Transaction + +You can sign and broadcast a transaction with the following command. + +```bash +gnokey maketx {SUB_COMMAND} {ADDRESS or KeyName} +``` + +#### **Subcommands** + +| Name | Description | +| -------- | ---------------------------- | +| `addpkg` | Uploads a new package. | +| `call` | Calls a public function. | +| `send` | The amount of coins to send. | + +### `addpkg` + +This subcommand lets you upload a new package. + +```bash +gnokey maketx addpkg \ + -deposit="1ugnot" \ + -gas-fee="1ugnot" \ + -gas-wanted="5000000" \ + -pkgpath={Registered Realm path} \ + -pkgdir={Package folder path} \ + {ADDRESS} \ + > unsigned.tx +``` + +#### **SignBroadcast Options** + +| Name | Type | Description | +| ------------ | ------- | ------------------------------------------------------------------------ | +| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | +| `gas-fee` | String | The gas fee to pay for the transaction. | +| `memo` | String | Any descriptive text. | +| `broadcast` | Boolean | Broadcasts the transaction. | +| `chainid` | String | Defines the chainid to sign for (should only be used with `--broadcast`) | + +#### **makeAddPackageTx Options** + +| Name | Type | Description | +| --------- | ------ | ------------------------------------- | +| `pkgpath` | String | The package path (required). | +| `pkgdir` | String | The path to package files (required). | +| `deposit` | String | The amount of coins to send. | + + +### `call` + +This subcommand lets you call a public function. + +```bash +# Register +gnokey maketx call \ + -gas-fee="1ugnot" \ + -gas-wanted="5000000" \ + -pkgpath="gno.land/r/demo/users" \ + -send="200000000ugnot" \ + -func="Register" \ + -args="" \ + -args={NAME} \ + -args="" \ + {ADDRESS} \ + > unsigned.tx +``` + +#### **SignBroadcast Options** + +| Name | Type | Description | +| ------------ | ------- | ---------------------------------------------------------------- | +| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | +| `gas-fee` | String | The gas fee to pay for the transaction. | +| `memo` | String | Any descriptive text. | +| `broadcast` | Boolean | Broadcasts the transaction. | +| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | + +#### **makeCallTx Options** + +| Name | Type | Description | +| --------- | ---------- | -------------------------------- | +| `send` | String | The amount of coins to send. | +| `pkgpath` | String | The package path (required). | +| `func` | String | The contract to call (required). | +| `args` | String \[] | The arguments of the contract. | + + +### `send` + +This subcommand lets you send a native currency to an address. + +```bash +gnokey maketx send \ + -gas-fee="1ugnot" \ + -gas-wanted="5000000" \ + -send={SEND_AMOUNT} \ + -to={TO_ADDRESS} \ + {ADDRESS} \ + > unsigned.tx +``` + +#### **SignBroadcast Options** + +| Name | Type | Description | +| ------------ | ------- | ----------------------------------------------------- | +| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | +| `gas-fee` | String | The gas fee to pay for the transaction. | +| `memo` | String | Any descriptive text. | +| `broadcast` | Boolean | Broadcasts the transaction. | +| `chainid` | String | The chainid to sign for (implies `--broadcast`) | + +#### **makeSendTx Options** + +| Name | Type | Description | +| ------ | ------ | ------------------------ | +| `send` | String | Amount of coins to send. | +| `to` | String | The destination address. | + + +## Sign a Document + +Sign a document with the following command. + +```bash +gnokey sign +``` + +#### **Options** + +| Name | Type | Description | +| ---------------- | ------- | ---------------------------------------------------------- | +| `txpath` | String | The path to file of tx to sign (default: `-`). | +| `chainid` | String | The chainid to sign for (default: `dev`). | +| `number` | UInt | The account number of the account to sign with (required) | +| `sequence` | UInt | The sequence number of the account to sign with (required) | +| `show-signbytes` | Boolean | Shows signature bytes. | + + +## Verify a Document Signature + +Verify a document signature with the following command. + +```bash +gnokey verify +``` + +#### **Options** + +| Name | Type | Description | +| --------- | ------ | ---------------------------------------- | +| `docpath` | String | The path of the document file to verify. | + +## Broadcast a Signed Document + +Broadcast a signed document with the following command. + +```bash +gnokey broadcast {signed transaction file document} +``` diff --git a/docs/explanation/gno-tooling/cli/gnoland.md b/docs/explanation/gno-tooling/cli/gnoland.md new file mode 100644 index 00000000000..3c05897646b --- /dev/null +++ b/docs/explanation/gno-tooling/cli/gnoland.md @@ -0,0 +1,31 @@ +--- +id: gno-tooling-gnoland +--- + +# gnoland + +## Run a Gnoland Node + +Start a node on the Gnoland blockchain with the following command. + +```bash +gnoland +``` + +### **Sub Commands** +| Command | Description | +| --------- | ----------------- | +| `start` | Run the full node | + + +### **Options** + +| Name | Type | Description | +| -------------------------- | ------- | --------------------------------------------------------------------------------------- | +| `chainid` | String | The id of the chain (default: `dev`). | +| `genesis-balances-file` | String | The initial GNOT distribution file (default: `./gnoland/genesis/genesis_balances.txt`). | +| `genesis-remote` | String | Replacement '%%REMOTE%%' in genesis (default: `"localhost:26657"`). | +| `genesis-txs-file` | String | Initial txs to be executed (default: `"./gnoland/genesis/genesis_txs.txt"`). | +| `root-dir` | String | directory for config and data (default: `testdir`). | +| `skip-failing-genesis-txs` | Boolean | Skips transactions that fail from the `genesis-txs-file` | +| `skip-start` | Boolean | Quits after initialization without starting the node. | diff --git a/docs/explanation/gno-tooling/cli/tm2txsync.md b/docs/explanation/gno-tooling/cli/tm2txsync.md new file mode 100644 index 00000000000..e9d616bcea5 --- /dev/null +++ b/docs/explanation/gno-tooling/cli/tm2txsync.md @@ -0,0 +1,45 @@ +--- +id: gno-tooling-tm2txsync +--- + +# tm2txsync + +`tm2txsync` is used for backing up a local node's transactions. + +## Import Transaction Data To (or Export It From) a Node + +You may import or export transaction data with the following command. + +```bash +tm2txsync {SUB_COMMAND} +``` + +#### **Subcommands** + +| Name | Description | +| -------- | -------------------------- | +| `export` | Exports txs from the node. | +| `import` | Imports txs to the node. | + +### Import + +#### **Options** + +| Name | Type | Description | +| -------- | ------ | ----------------------------------------------------------------- | +| `remote` | String | The Remote RPC in `addr:port` format (default: `localhost:26657`) | +| `in` | String | The input file path (default: `txexport.log`) | + +### Export + +#### **Options** + +| Name | Type | Description | +| -------- | ------- | ----------------------------------------------------------------- | +| `remote` | String | The Remote RPC in `addr:port` format (default: `localhost:26657`) | +| `start` | Int64 | Starting height (default: `1`) | +| `tail` | Int64 | Start at LAST - N. | +| `end` | Int64 | End height (optional) | +| `out` | String | The output file path (default: `txexport.log`) | +| `quiet` | Boolean | Quiet mode. | +| `follow` | Boolean | Keep attached and follow new events. | diff --git a/docs/explanation/gnovm.md b/docs/explanation/gnovm.md new file mode 100644 index 00000000000..fb767ff316a --- /dev/null +++ b/docs/explanation/gnovm.md @@ -0,0 +1,27 @@ +--- +id: gnovm +--- + +# GnoVM + +GnoVM is a virtual machine that interprets Gnolang, a custom version of Golang optimized for blockchains, featuring automatic state management, full determinism, and idiomatic Go. +It works with Tendermint2 and enables smarter, more modular, and transparent appchains with embedded smart-contracts. +It can be adapted for use in TendermintCore, forks, and non-Cosmos blockchains. + +Read the ["Intro to Gnoland"](https://test3.gno.land/r/gnoland/blog:p/intro) blogpost. + +This folder focuses on the VM, language, stdlibs, tests, and tools, independent of the blockchain. +This enables non-web3 developers to contribute without requiring an understanding of the broader context. + +## Language Features + +* Like interpreted Go, but more ambitious. +* Completely deterministic, for complete accountability. +* Transactional persistence across data realms. +* Designed for concurrent blockchain smart contracts systems. + +## Getting started + +Install [`gno`](./cmd/gno) and refer to the [`examples`](../examples) folder to start developing contracts. + +Check the [Makefile](./Makefile) to enhance GnoVM, Gnolang, and stdlibs. diff --git a/docs/explanation/packages.md b/docs/explanation/packages.md new file mode 100644 index 00000000000..85443052a6a --- /dev/null +++ b/docs/explanation/packages.md @@ -0,0 +1,113 @@ +--- +id: packages +--- + +# Packages + +Packages encompass functionalities that are more closely aligned with the characteristics and capabilities of realms, as opposed to standard libraries. + +The full list of available packages can be found in [the demo package](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo). Below are some of the most commonly used packages. + +## `avl` + +In Golang, the classic key/value data type is represented by the `map` construct. However, while Gnolang also supports the use of `map`, it is not a viable option as it lacks determinism due to its non-sequential nature. + +To address this issue, Gnolang implements the [AVL Tree](https://en.wikipedia.org/wiki/AVL\_tree) (Adelson-Velsky-Landis Tree) as a solution. The AVL Tree is a self-balancing binary search tree. + +The `avl` package comprises a set of functions that can manipulate the leaves and nodes of the AVL Tree. + +## `grc20` + +Gnolang includes an implementation of the `erc20` fungible token standard referred to as `grc20`. The interfaces of `grc20` are as follows: + +[embedmd]:# (../assets/explanation/packages/pkg-1.gno go) +```go +func TotalSupply() uint64 +func BalanceOf(account std.Address) uint64 +func Transfer(to std.Address, amount uint64) +func Approve(spender std.Address, amount uint64) +func TransferFrom(from, to std.Address, amount uint64) +func Allowance(owner, spender std.Address) uint64 +``` + +The role of each function is as follows: + +* `TotalSupply`: Returns the total supply of the token. +* `BalanceOf`: Returns the balance of tokens of an account. +* `Transfer`: Transfers specific `amount` of tokens from the `caller` of the function to the `to` address. +* `Approve`: Grants the `spender`(also referred to as `operator`) with the ability to send specific `amount` of the `caller`'s (also referred to as `owner`) tokens on behalf of the `caller`. +* `TransferFrom`: Can be called by the `operator` to send specific `amount` of `owner`'s tokens from the `owner`'s address to the `to` address. +* `Allowance`: Returns the number of tokens approved to the `spender` by the `owner`. + +Two types of contracts exist in`grc20`: + +1. `AdminToken`\ + \- Implements the token factory with `Helper` functions.\ + \- The underlying struct should not be exposed to users. However, it can be typecasted as UserToken using the `GRC20()` method. +2. `UserToken`\ + \- Implements the `IGRC20` interface.\ + \- The underlying struct can be exposed to users. Created with the `GRC20()` method of `adminToken`. + +## `grc721` + +Gnolang includes an implementation of the `erc721` non-fungible token standard referred to as `grc721`. The interfaces of `grc721` are as follows: + +[embedmd]:# (../assets/explanation/packages/pkg-2.gno go) +```go +// functions that work similarly to those of grc20 +func BalanceOf(owner std.Address) (uint64, error) +func Approve(approved std.Address, tid TokenID) error +func TransferFrom(from, to std.Address, tid TokenID) error + +// functions unique to grc721 +func OwnerOf(tid TokenID) (std.Address, error) +func SafeTransferFrom(from, to std.Address, tid TokenID) error +func SetApprovalForAll(operator std.Address, approved bool) error +func GetApproved(tid TokenID) (std.Address, error) +func IsApprovedForAll(owner, operator std.Address) bool +``` + +`grc721` contains a new set of functions: + +* `OwnerOf`: Returns the `owner`'s address of a token specified by its `TokenID`. +* `SafeTransferFrom`: Equivalent to the `TransferFrom` function of `grc20`. + * The `Safe` prefix indicates that the function runs a check to ensure that the `to` address is a valid address that can receive tokens. + * As you can see from the [code](https://github.com/gnolang/gno/blob/master/examples/gno.land/p/demo/grc/grc721/basic\_nft.gno#L341), the concept of `Safe` has yet to be implemented. +* `SetApprovalForAll`: Approves all tokens owned by the `owner` to an `operator`. + * You may not set multiple `operator`s. +* `GetApproved`: Returns the `address` of the `operator` for a token, specified with its `ID`. +* `IsApprovedForAll`: Returns if all NFTs of the `owner` have been approved to the `operator`. + +## `testutils` + +The `testutils` package contains a set of functions that comes in handy when testing realms. The sample function below is the commonly used `TestAddress` function that generates a random address. + +[embedmd]:# (../assets/explanation/packages/pkg-3.gno go) +```go +func TestAddress(name string) std.Address { + if len(name) > std.RawAddressSize { + panic("address name cannot be greater than std.AddressSize bytes") + } + addr := std.RawAddress{} + // TODO: use strings.RepeatString or similar. + // NOTE: I miss python's "".Join(). + blanks := "____________________" + copy(addr[:], []byte(blanks)) + copy(addr[:], []byte(name)) + return std.Address(std.EncodeBech32("g", addr)) +} +``` + +The code takes the `name` as the input and creates a random address. Below is a list of examples where it's used in the test case of the `foo20` realm. + +[embedmd]:# (../assets/explanation/packages/pkg-4.gno go) +```go +admin := users.AddressOrName("g1tntwtvzrkt2gex69f0pttan0fp05zmeg5yykv8") +test2 := users.AddressOrName(testutils.TestAddress("test2")) +recv := users.AddressOrName(testutils.TestAddress("recv")) +normal := users.AddressOrName(testutils.TestAddress("normal")) +owner := users.AddressOrName(testutils.TestAddress("owner")) +spender := users.AddressOrName(testutils.TestAddress("spender")) +recv2 := users.AddressOrName(testutils.TestAddress("recv2")) +mibu := users.AddressOrName(testutils.TestAddress("mint_burn")) +``` diff --git a/docs/proof-of-contribution.md b/docs/explanation/proof-of-contribution.md similarity index 99% rename from docs/proof-of-contribution.md rename to docs/explanation/proof-of-contribution.md index e839a3cdf1f..364a5eb254d 100644 --- a/docs/proof-of-contribution.md +++ b/docs/explanation/proof-of-contribution.md @@ -1,3 +1,7 @@ +--- +id: proof-of-contribution +--- + # Proof of Contribution The gno.land chain utilizes a reputation-based consensus mechanism instead of proof-of-stake. diff --git a/docs/explanation/realms.md b/docs/explanation/realms.md new file mode 100644 index 00000000000..fd6969db3f1 --- /dev/null +++ b/docs/explanation/realms.md @@ -0,0 +1,49 @@ +--- +id: realms +--- + +# Realms + +A realm refers to a specific instance of a smart contract that can be written +in [Gnolang](./gno-language.md). The potentials of realms are endless - you can create virtually any +application in your mind with built-in composability, +transparency, and censorship resistance. Here are some ideas of what you can build with realms: + +* Self-custodial financial exchanges (decentralized exchanges). +* Lending platforms with better rates. +* Transparent insurance systems. +* Fair and accessible voting systems. +* Logistics and supply chain networks. + +## Packages vs Realms + +#### [**Pure Packages**](https://github.com/gnolang/gno/tree/master/examples/gno.land/p) + +* A unit that contains functionalities and utilities that can be used in realms. +* Packages are stateless. +* The default import path is `gno.land/p/~~~`. +* Can be imported to other realms or packages. +* Cannot import realms. + +#### [**Realms**](https://github.com/gnolang/gno/tree/master/examples/gno.land/r) + +* Smart contracts in Gnolang. +* Realms are stateful. +* Realms can own assets (tokens). +* The default import path is `gno.land/r/~~~`. +* Realms can implement `Render(path string) string` to simplify dapp frontend development by allowing users to request + markdown renderings from validators and full nodes without a transaction. + +A notable feature of realms is the `Render()` function. + +```go +package demo + +func Render(path string) string { + return "# Hello Gno!" +} +``` + +Upon calling the realm above, `# Hello Gno!` is printed with a string-typed `path` declared in an argument. It should be +noted that while the `path` argument included in the sample code is not utilized, it serves the purpose of +distinguishing the path during the rendering process. diff --git a/docs/explanation/tendermint2.md b/docs/explanation/tendermint2.md new file mode 100644 index 00000000000..3c86a46922a --- /dev/null +++ b/docs/explanation/tendermint2.md @@ -0,0 +1,122 @@ +--- +id: tendermint2 +--- + +# Tendermint2 + +**Disclaimer: Tendermint2 is currently part of the Gno monorepo for streamlined development.** + +**Once Gno.land is on the mainnet, Tendermint2 will operate independently, including for governance, +on https://github.com/tendermint/tendermint2.** + +## Problems + +* Open source is open for subversion. +* Incentives and mission are misaligned. +* Need directory & forum for Tendermint/SDK forks. + +## Partial Solution: adopt principles + +* Simplicity of design. +* The code is the spec. +* Minimal code - keep total footprint small. +* Minimal dependencies - all dependencies must get audited, and become part of + the repo. +* Modular dependencies - whereever reasonable, make components modular. +* Completeness - software projects that don't become finished are projects + that are forever vulnerable. One of the primary goals of the Gno language + and related works is to become finished within a reasonable timeframe. + +## What is already proposed for Tendermint2: + +* Complete Amino. -> multiplier of productivity for SDK development, to not + have to think about protobuf at all. Use "genproto" to even auto-generate + proto3 for encoding/decoding optimization through protoc. + - MISSION: be the basis for improving the encoding standard from proto3, because + proto3 length-prefixing is slow, and we need "proto4" or "amino2". + - LOOK at the auto-generated proto files! + https://github.com/gnolang/gno/blob/master/pkgs/bft/consensus/types/cstypes.proto + for example. + - There was work to remove this from the CosmosSDK because + Amino wasn't ready, but now that it is, it makes sense to incorporate it into + Tendermint2. + + +* Remove EvidenceReactor, Evidence, Violation: + + We need to make it easy to create alt mempool reactors. + + We "kill two birds with one stone" by implementing evidence as a first-class mempool lane. + + The authors of "ABCI++" have a different set of problems to solve, so we should do both! Tendermint++ + and Tendermint2. + + +* Fix address size to 20 bytes -> 160 is sufficient, and fixing it brings optimizations. + + +* General versionset system for handshake negotiation. -> So Tendermint2 can be + used as basis for other P2P applications. + + +* EventBus -> EventSwitch. -> For indexing, use an external system. + + To ensure Tendermint2 remains minimal and easily integrated with plugin modules, there is no internal implementation. + + The use of an EventSwitch makes the process simpler and synchronous, which maintains the determinism of Tendermint + tests. + + Keeping the Tendermint protocol synchronous is sufficient for optimal performance. + + However, if there is a need for asynchronous processing due to an exceptionally large number of validators, it should + be a separate fork with a unique name under the same taxonomy as Tendermint. + + +* Fix nondeterminism in consensus tests -> in relation to the above. + +* Add "MaxDataBytes" for total tx data size limitation. + + To avoid unexpected behavior caused by changes in validator size, it's best to allocate room for each module + separately instead of limiting the total block size as we did before. + +This way, we can ensure that there's enough space for all modules. + +* Remove external dependencies like prometheus + To ensure accuracy, all metrics and events should be integrated through interfaces. This may require extracting client + logic from Prometheus, but it will be incorporated into Tendermint2 and undergo the same auditing process as + everything else. + +* General consensus/WAL -> a WAL is useful enough to warrant being a re-usable + module. + +* Remove GRPC -> GRPC support should be plugged in (say in a GRPC fork of + Tendermint2), so alternative RPC protocols can likewise be. Tendermint2 aims + to be independent of the Protobuf stack so that it can retain freedom for + improving its codec. + +* Remove dependency on viper/cobra -> I have tried to strip out what we don't + use of viper/cobra for minimalism, but could not; and viper/cobra is one + prime target for malware to be introduced. Rather than audit viper/cobra, + Tendermint2 implements a cli convention for Go-structure-based flags and cli; + so if you still want to use viper/cobra you can do so by translating flags to + an options struct. + +* Question: Which projects use ABCI sockets besides CosmosSDK derived chains? + +## Roadmap + +First, we create a multi-organizational team for Tendermint2 & +TendermintCore/++ development. We will maintain a fork of the Tendermint++ repo +and suggest changes upstream based on our work on Tendermint2, while also +porting necessary fixes from Tendermint++ over to Tendermint2. + +We will also reach out to ecosystem partners and survey and create a +directory/taxonomy for Tendermint and CosmosSDK derivatives and manage a forum +for interfork collaboration. + +Ideally, Tendermint2 and TendermintCore/++ merge into one. + +## Challenge + +Either make a PR to Gaia/CosmosSDK/TendermintCore to be like Tendermint2, or +make a PR to Tendermint2 to import a feature or fix of TendermintCore. diff --git a/docs/getting-started/browsing-gno-source-code.md b/docs/getting-started/browsing-gno-source-code.md new file mode 100644 index 00000000000..24e7c173f95 --- /dev/null +++ b/docs/getting-started/browsing-gno-source-code.md @@ -0,0 +1,96 @@ +--- +id: browsing-gno-source-code +--- + +# Browsing Gno Source Code + +## Overview + +In this tutorial, you will learn how to browse deployed Gno [Realms](../explanation/realms.md) +and [Packages](../explanation/packages.md). Additionally, you will understand how the `Render` method is utilized +to achieve Realm state visibility. + +## Prerequisites + +- **`gnoweb` set up. Reference the [Local Setup](local-setup.md#3-installing-other-gno-tools) guide for steps** + +## 1. Start the local chain + +In order for `gnoweb` to fetch realm and package source code, it needs to connect a running Gno node. For a better +overview on running a local node, please reference the [Starting a Local Chain](setting-up-a-local-chain.md) guide. + +In this guide, we will start a local node with the default configuration. Navigate to the `gno.land` sub-folder and run: + +```bash +gnoland start +``` + +## 2. Start `gnoweb` + +Now that the chain is running, we can start the `gnoweb` tool: + +```bash +gnoweb +``` + +:::info Different JSON-RPC URL + +In case you are running a node on a different JSON-RPC URL from the default one (`http://127.0.0.1:26657`), +you can specify the remote URL with the `gnoweb` flag named `--remote` + +::: + +We should be able to access the website locally on http://127.0.0.1:8888/. + +![gnoweb screen](../assets/getting-started/browsing-gno-source-code/gnoweb.png) + +## 3. Browse Package source code + +Packages in Gno.land usually have names resembling `gno.land/p/`. Since packages do not contain state, we can only +view their source code on-chain. To learn more about Packages, please check out +the [Packages](../explanation/packages.md) explanation document. + +Using `gnoweb`, we can browse the source code in our browser. +For example, the `avl` package is deployed at `gno.land/p/demo/avl`. + +To access the source code of the `avl` package, we can append the `/p/demo/avl` to our browser URL (from the homepage). + +The final URL for the `avl` package source could be viewable at http://127.0.0.1:8888/p/demo/avl, if we followed +default setup params, as we did in this guide. + +![gnoweb avl](../assets/getting-started/browsing-gno-source-code/gnoweb-avl.png) + +From here, we can open any source code file of the deployed on-chain package and inspect its API. + +## 4. Browse Realm source code + +In contrast to Packages, Realms in Gno.land usually have names resembling `gno.land/r/`. + +Realms _do_ contain state, and in addition to being able to view their source code on-chain, users can also view their +internal state representation in the form of the `Render()` output. To learn more about Realms, please +check out the [Realms](../explanation/realms.md) explanation document. + +Using `gnoweb`, we can browse the Realm `Render()` method output and source code in our browser. +For example, the `boards` Realm is deployed at `gno.land/r/demo/boards`. + +To view the internal Realm state of the `boards` package, we can append the `/r/demo/boards` to our browser URL (from +the homepage). + +The final URL for the `boards` Realm internal state could be viewable at http://127.0.0.1:8888/r/demo/boards, if we +followed +default setup params, as we did in this guide. + +![gnoweb boards](../assets/getting-started/browsing-gno-source-code/gnoweb-boards.png) + +:::info Render() is not required + +Internal Realm state does not have to be exposed through the `Render()` method of the realm, as it is +not a requirement for deploying a Realm. + +::: + +Additionally, to view the source code for the realm, we simply need to append the `/` to the full realm path: + +http://127.0.0.1:8888/r/demo/boards/ + +![gnoweb boards source](../assets/getting-started/browsing-gno-source-code/gnoweb-boards-source.png) diff --git a/docs/getting-started/local-setup.md b/docs/getting-started/local-setup.md new file mode 100644 index 00000000000..cbef4522477 --- /dev/null +++ b/docs/getting-started/local-setup.md @@ -0,0 +1,111 @@ +--- +id: local-setup +--- + +# Local Setup + +## 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 install all the necessary tooling, +and validate that they are correctly configured to run on your machine. + +## Prerequisites + +- **Git** +- **`make` (for running Makefiles)** +- **Go 19+** + +## 1. Cloning the repository + +To get started with a local Gno.land development environment, you must clone the GitHub repository +somewhere on disk: + +```bash +git clone https://github.com/gnolang/gno.git +``` + +## 2. Installing the `gno` development toolkit + +Next, we are going to build and install the `gno` development toolkit. +`gno` provides ample functionality to the user, among which is running, precompiling, testing and building `.gno` files. + +To install the toolkit, navigate to the `gnovm` folder from the repository root, and run the `build` make directive: + +```bash +cd gnovm +make build +``` + +This will build out the necessary `gno` binary into the `gnovm/cmd` sub-folder: + +![gno tool build](../assets/getting-started/local-setup/make-build-gnovm.gif) + +Next, to make development easier, we need to make the binary available system-wide. +From the same `gnovm` sub-folder, you can run: + +```bash +make install +``` + +To verify the `gno` binary is installed system-wide, you can run: + +```bash +gno --help +``` + +You should get the help output from the command: + +![gno help](../assets/getting-started/local-setup/gno-help.gif) + +Alternatively, if you don't want to have the binary callable system-wide, you can run the binary directly: + +```bash +go run ./cmd/gno --help +``` + +## 3. Installing other `gno` tools + +The next step is to install several other tools that are required for the Gno development environment, like + +- `gnoland` - the Gno [blockchain node](setting-up-a-local-chain.md) +- `gnokey` - the Gno [private key manager](working-with-key-pairs.md) +- `gnoweb` - the Gno [source code viewer](browsing-gno-source-code.md) +- `gnofaucet` - the Gno [native currency faucet](setting-up-funds/running-a-faucet.md) + +To build these tools, from the root directory navigate to the `gno.land` sub-folder, and run the `build` make +directive: + +```bash +cd gno.land +make build +``` + +This will build out the necessary binaries into the `gno.land/cmd` sub-folder: + +![gno tools build](../assets/getting-started/local-setup/make-build-gnoland.gif) + +Same as with the `gno` tool, we can make these binaries available system-wide. +From the same `gno.land` sub-folder, you can run: + +```bash +make install +``` + +To verify that, for example, the `gnokey` binary is installed system-wide, you can run: + +```bash +gnokey --help +``` + +You should get the help output from the command: + +![gnokey help](../assets/getting-started/local-setup/gnokey-help.gif) + +## Conclusion + +That's it 🎉 + +You have successfully built out and installed the necessary tools for Gno development! + +In further documents, you will gain a better understanding on how they are used to make Gno work. diff --git a/docs/getting-started/setting-up-a-local-chain.md b/docs/getting-started/setting-up-a-local-chain.md new file mode 100644 index 00000000000..51fe1fd45a6 --- /dev/null +++ b/docs/getting-started/setting-up-a-local-chain.md @@ -0,0 +1,142 @@ +--- +id: setting-up-a-local-chain +--- + +# Setting up a Local Chain + +## Overview + +In this tutorial, you will learn how to start a local Gno node (and chain!). +Additionally, you will see the different options you can use to make your Gno instance unique. + +## Prerequisites + +- [`gnoland` installed](local-setup.md#3-installing-other-gno-tools). + +## Starting a node with a default configuration + +You can start a Gno blockchain node with the default configuration by navigating to the `gno.land` sub-folder and +running the following command: + +```bash +gnoland start +``` + +The command will trigger a chain initialization process (if you haven't run the node before), and start the Gno node, +which is ready to accept transactions and interact with other Gno nodes. + +![gnoland start](../assets/getting-started/setting-up-a-local-chain/gnoland-start.gif) + +To view the command defaults, simply run the `help` command: + +```bash +gnoland start --help +``` + +Let's break down the most important default settings: + +- `chainid` - the ID of the Gno chain. This is used for Gno clients, and distinguishing the chain from other Gno + chains (ex. through IBC) +- `config` - the custom node configuration file + for more details on utilizing this file +- `genesis-balances-file` - the initial premine balances file, which contains initial native currency allocations for + the chain. By default, the genesis balances file is located in `gno.land/genesis/genesis_txs.txt`, this is also the + reason why we need to navigate to the `gno.land` sub-folder to run the command with default settings +- `root-dir` - the working directory for the node configuration and node data (state DB) + +:::info Resetting the chain + +As mentioned, the working directory for the node is located in `root-dir`. To reset the chain, you need +to delete this directory and start the node up again. If you are using the default node configuration, you can run +`make fclean` from the `gno.land` sub-folder to delete the `tempdir` working directory. + +::: + +## Changing the chain ID + +:::info Changing the Gno chain ID has several implications + +- It affects how the Gno node communicates with other Gno nodes / chains +- Gno clients that communicate through JSON-RPC need to match this value + +It's important to configure your node properly before launching it in a distributed network. +Keep in mind that changes may not be applicable once connected. + +::: + +To change the Gno chain ID, run the following command: + +```bash +gnoland start --chainid NewChainID +``` + +We can verify the chain ID has been changed, by fetching the status of the node and seeing the +associated chain ID. By default, the node exposes the JSON-RPC API on `http://127.0.0.1:26657`: + +```bash +curl -H "Content-type: application/json" -d '{ + "jsonrpc": "2.0", + "method": "status", + "params": [], + "id": 1 +}' 'http://127.0.0.1:26657' +``` + +We should get a response similar to this: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "node_info": { + "version_set": [ + // ... + ], + "net_address": "g10g9r37g9xa54a6clttzmhk2gmdkzsntzty0cvr@0.0.0.0:26656", + "network": "NewChainID" + // ... + } + } +} +``` + +:::danger Chain ID can be set only once + +Since the chain ID information is something bound to a chain, you can +only change it once upon chain initialization, and further attempts to change it will +have no effect. + +::: + +## Changing the node configuration + +You can specify a node configuration file using the `--config` flag. + +```bash +gnoland start --config config.toml +``` + +## Changing the premine list + +You do not need to use the `gno.land/genesis/genesis_balances.txt` file as the source of truth for initial network +funds. + +To specify a custom balance sheet for a fresh local chain, you can use the `-genesis-balances-file`: + +```bash +gnoland start -genesis-balances-file custom-balances.txt +``` + +Make sure the balances file follows the following format: + +```text +
=ugnot +``` + +Following this pattern, potential entries into the genesis balances file would look like: + +```text +g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt=10000000000ugnot +g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq=10000000000ugnot +``` diff --git a/docs/getting-started/setting-up-funds/premining-balances.md b/docs/getting-started/setting-up-funds/premining-balances.md new file mode 100644 index 00000000000..00076cdfda1 --- /dev/null +++ b/docs/getting-started/setting-up-funds/premining-balances.md @@ -0,0 +1,106 @@ +--- +id: premining-balances +--- + +# Premining Balances + +## Overview + +In this tutorial, you will gain an understanding on how to premine native currency on a local Gno.land chain. +Additionally, you will understand how to query the account balance after you premine it. + +Premining balance is the process of making sure some accounts (addresses) have specific funds when the chain initially +launches. In the context of local chain deployments, premine balances are used to ensure the user accounts (developers) +have ample funds to interact with the chain and facilitate contract deployments. + +## Prerequisites + +- **`gnoland` and `gnokey` set up. Reference the [Installation](../local-setup.md#3-installing-other-gno-tools) guide + for steps** + +## 1. Clean chain data + +In order for us to premine funds on a fresh chain, we need to make sure we do not have any leftover blockchain data +from previous chain runs. + +The blockchain node, when it runs, works with an embedded DB locally on disk to store execution data (such as +configuration files, or the state DB). For Gno blockchain nodes, this working directory is labeled as `testdir` by +default. + +To clean out old blockchain data, navigate to the `gno.land` folder and run the appropriate make command: + +```bash +cd gno.land +make fclean +``` + +## 2. Change the `genesis_balances.txt` file + +When the Gno node boots up, among other things, it reads a file called `genesis_balances.txt` to generate the initial +balance set for the blockchain. + +An example of how this looks like in the initial `genesis.json` file after the chain starts: + +```bash + "app_state": { + "@type": "/gno.GenesisState", + "balances": [ + "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5=10000000000000ugnot", + "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj=10000000000000ugnot", + "g1f4v282mwyhu29afke4vq5r2xzcm6z3ftnugcnv=1000000000000ugnot", + "g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa=1000000000000ugnot" + ], +``` + +The `genesis_balances.txt` file is located at `./gno.land/genesis/genesis_balances.txt`. + +To add a new entry to the premine table, simply append a line to the end of the file: + +```bash +g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt=10000000000ugnot # My address +``` + +Replace `g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt` with the address you want balance on, and `10000000000ugnot` with the +desired `ugnot` balance. + +## 3. Start the local chain + +Now that our address and the desired premine balance are located in the `genesis_balances.txt` file, we can start the +local Gno node. + +To run the local Gno node, make sure you are in the `gno.land` sub-folder, and run the appropriate make command: + +```bash +cd gno.land +gnoland start +``` + +This command will initialize the Gno node, generate the `genesis.json` with our newly added premine information, and +start the chain. + +![gnoland start](../../assets/getting-started/setting-up-funds/gnoland-start.gif) + +## 3. Check the account balance + +To check the balance of any account (or the account we just premined), we can use the following ABCI query: + +```bash +gnokey query --remote localhost:26657 bank/balances/g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt +``` + +Let's break down this command: + +- **`--remote`** - the JSON-RPC URL of the running Gno node. In the case of a local deployment, the default value + is `localhost:26657` +- **`bank/balances/g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt`** - the ABCI query targets the `bank` module to find + the `balances` for address `g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt`. Replace the address with your desired address + +![gnokey query](../../assets/getting-started/setting-up-funds/gnokey-query.gif) + +## Conclusion + +That's it 🎉 + +You have successfully premined a native currency balance on a locally-running Gno chain! +Additionally, you have also learned how to query the native currency balance for an address, using built-in ABCI queries +and the `gnokey` tool. diff --git a/docs/getting-started/setting-up-funds/running-a-faucet.md b/docs/getting-started/setting-up-funds/running-a-faucet.md new file mode 100644 index 00000000000..3bd0e55b791 --- /dev/null +++ b/docs/getting-started/setting-up-funds/running-a-faucet.md @@ -0,0 +1,100 @@ +--- +id: running-a-faucet +--- + +# Running a Faucet + +## Overview + +In this tutorial, we will cover how to run a local native currency faucet that works seamlessly with a Gno node. +Using the faucet, any address can get a hold of native currency funds in case they +haven't [premined a balance beforehand](premining-balances.md). + +## Prerequisites + +- **`gnoland`, `gnofaucet` and `gnoweb` set up. Reference + the [Installation](../local-setup.md#3-installing-other-gno-tools) guide for steps** + +## 1. Ensure a topped-up faucet address + +The Gno faucet works by designating a single address as a faucet address that will distribute funds. + +Ensure the faucet account will have enough funds by [premining its balance](premining-balances.md) to a high value. +In case you do not have an existing address added to `gnokey`, you can consult +the [Working with Key Pairs](../working-with-key-pairs.md) guide. + +## 2. Start the local chain + +After ensuring the faucet address will have enough funds in the premine, we +can [run the local blockchain node](../setting-up-a-local-chain.md). +Navigate to the `gno.land` sub-folder and run the appropriate make command: + +```bash +cd gno.land +gnoland start +``` + +## 3. Start the faucet + +After the chain is up and running locally, you can start the faucet by running the following command: + +```bash +gnofaucet serve --chain-id dev MyKey +``` + +The command will prompt you to enter the decryption password for the key you've provided. + +- **`--chain-id`** - the chain ID of the local running node. The default value is `dev` +- **`MyKey`** - the name of the faucet key (you can also use the address) we premined in + the [previous steps](#1-ensure-a-topped-up-faucet-address) + +This will initialize the faucet to listen on port `5050`, by default. + +![gnofaucet serve](../../assets/getting-started/setting-up-funds/gnofaucet-serve.gif) + +## 4. Start the `gnoweb` interface + +To access the faucet UI, we need to start the local `gnoweb` interface. + +Navigate to the `gno.land` subfolder, and run the appropriate binary: + +```bash +cd gno.land +gnoweb +``` + +This will initialize the `gnoweb` interface on `http://127.0.0.1:8888`. + +![gnoweb](../../assets/getting-started/setting-up-funds/gnoweb.gif) + +## 5. Use the deployed faucet + +Once `gnoweb` has been started, you can navigate to http://127.0.0.1:8888/faucet. + +Simply input the desired address you wish to receive funds on (`1 GNOT` by default), and press the `GO` button. + +![gnofaucet page](../../assets/getting-started/setting-up-funds/faucet-page.png) + +After you've added the address, you should see a success message in the browser: + +``` +faucet success +``` + +In the terminal where `gnofaucet` is running, you should see a success message as well, something along the lines of: + +```bash +will deliver: {"msg":[{"@type":"/bank.MsgSend","from_address":"g155n659f89cfak0zgy575yqma64sm4tv6exqk99","to_address":"g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt","amount":"1000000ugnot"}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A10ufcOV5WP71K+KvLagJi+3TSCkx8EWKep3NbjVclU8"},"signature":"7Y0hkdPBruzMiANAHXWx3luAMhQN6SF3AQtstvOSZJI5P4uep8RIntw2c8W5blFiCd9HoMiEZFNf5dgWYwkjmA=="}],"memo":""} + +OK! +GAS WANTED: 50000 +GAS USED: 41971 +127.0.0.1 faucet success +``` + +## Conclusion + +That's it 🎉 + +You have successfully set up a GNOT faucet on a locally-running Gno chain! +Additionally, you have also learned how to utilize the `gnoweb` tool for a visual faucet UI. diff --git a/docs/getting-started/working-with-key-pairs.md b/docs/getting-started/working-with-key-pairs.md new file mode 100644 index 00000000000..5617e8339eb --- /dev/null +++ b/docs/getting-started/working-with-key-pairs.md @@ -0,0 +1,191 @@ +--- +id: working-with-key-pairs +--- + +# Working with Key Pairs + +## Overview + +In this tutorial, you will learn how to manage private user keys, which are required for interacting with the Gno.land +blockchain. You will understand what mnemonics are, how they are used, and how you can make interaction seamless with +Gno. + +## Prerequisites + +- **`gnokey` set up. Reference the [Local Setup](local-setup.md#3-installing-other-gno-tools) guide for steps** + +## Listing available keys + +`gnokey` works by creating a local directory in the filesystem for storing (encrypted!) user private keys. + +You can find this repository by checking the value of the `--home` flag when running the following command: + +```bash +gnokey --help +``` + +Example output: + +```bash +USAGE + [flags] [...] + +Manages private keys for the node + +SUBCOMMANDS + add Adds key to the keybase + delete Deletes a key from the keybase + generate Generates a bip39 mnemonic + export Exports private key armor + import Imports encrypted private key armor + list Lists all keys in the keybase + sign Signs the document + verify Verifies the document signature + query Makes an ABCI query + broadcast Broadcasts a signed document + maketx Composes a tx document to sign + +FLAGS + -config ... config file (optional) + -home $XDG_CONFIG/gno home directory + -insecure-password-stdin=false WARNING! take password from stdin + -quiet=false suppress output during execution + -remote 127.0.0.1:26657 remote node URL + +error parsing commandline arguments: flag: help requested +``` + +In this example, the directory where `gnokey` will store working data +is `/Users/zmilos/Library/Application Support/gno`. + +Keep note of this directory, in case you need to reset the keystore, or migrate it for some reason. +You can provide a specific `gnokey` working directory using the `--home` flag. + +To list keys currently present in the keystore, we can run: + +```bash +gnokey list +``` + +In case there are no keys present in the keystore, the command will simply return an empty response. +Otherwise, it will return the list of keys and their accompanying metadata as a list, for example: + +```bash +0. Manfred (local) - addr: g15uk9d6feap7z078ttcnwc94k60ullrvhmynxjt pub: gpub1pgfj7ard9eg82cjtv4u4xetrwqer2dntxyfzxz3pqvn87u43scec4zfgn4la3nt237nehzydzayqxe43fx63lq6rty9c5almet4, path: +1. Milos (local) - addr: g15lppu0tuxets0c0t80tncs4enqzgxt7v4eftcj pub: gpub1pgfj7ard9eg82cjtv4u4xetrwqer2dntxyfzxz3pqw2kkzujprgrfg7vumg85mccsf790n5ep6htpygkuwedwuumf2g7ydm4vqf, path: +``` + +The key response consists of a few pieces of information: + +- The name of the private key +- The derived address (`addr`) +- The public key (`pub`) + +Using these pieces of information, we can interact with Gno.land tools and write blockchain applications. + +## Generating a BIP39 mnemonic + +Using `gnokey`, we can generate a [mnemonic phrase](https://en.bitcoin.it/wiki/Seed_phrase) based on +the [BIP39 standard](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). + +To generate the mnemonic phrase in the console, you can run: + +```bash +gnokey generate +``` + +![gnokey generate](../assets/getting-started/creating-a-key-pair/gnokey-generate.gif) + +## Adding a random private key + +If we wanted to add a new private key to the keystore, we can run the following command: + +```bash +gnokey add MyKey +``` + +Of course, you can replace `MyKey` with whatever name you want for your key. + +The `gnokey` tool will prompt you to enter a password to encrypt the key on disk (don't forget this!). +After you enter the password, the `gnokey` tool will add the key to the keystore, and return the accompanying [mnemonic +phrase](https://en.bitcoin.it/wiki/Seed_phrase), which you should remember somewhere if you want to recover the key at a +future point in time. + +![gnokey add random](../assets/getting-started/creating-a-key-pair/gnokey-add-random.gif) + +You can check that the key was indeed added to the keystore, by listing available keys: + +```bash +gnokey list +``` + +![gnokey list](../assets/getting-started/creating-a-key-pair/gnokey-list.gif) + +## Adding a private key using a mnemonic + +To add a private key to the `gnokey` keystore [using an existing mnemonic](#generating-a-bip39-mnemonic), we can run the +following command with the +`--recover` flag: + +```bash +gnokey add --recover MyKey +``` + +Of course, you can replace `MyKey` with whatever name you want for your key. + +By following the prompts to encrypt the key on disk, and providing a BIP39 mnemonic, we can successfully add +the key to the keystore. + +![gnokey add mnemonic](../assets/getting-started/creating-a-key-pair/gnokey-add-mnemonic.gif) + +## Deleting a private key + +To delete a private key from the `gnokey` keystore, we need to know the name or address of the key to remove. +After we have this information, we can run the following command: + +```bash +gnokey delete MyKey +``` + +After entering the key decryption password, the key will be deleted from the keystore. + +:::caution Recovering a private key +In case you delete or lose access to your private key in the `gnokey` keystore, you +can recover it using the key's mnemonic, or by importing it if it was exported at a previous point in time. +::: + +## Exporting a private key + +Private keys stored in the `gnokey` keystore can be exported to a desired place +on the user's filesystem. + +Keys are exported in their original armor, encrypted or unencrypted. + +To export a key from the keystore, you can run: + +```bash +gnokey export -key MyKey -output-path ~/Work/gno-key.asc +``` + +Follow the prompts presented in the terminal. Namely, you will be asked to decrypt the key in the keystore, +and later to encrypt the armor file on disk. It is worth noting that you can also export unencrypted key armor, using +the `--unsafe` flag. + +![gnokey export](../assets/getting-started/creating-a-key-pair/gnokey-export.gif) + +## Importing a private key + +If you have an exported private key file, you can import it into `gnokey` fairly easily. + +For example, if the key is exported at `~/Work/gno-key.asc`, you can run the following command: + +```bash +gnokey import -armor-path ~/Work/gno-key.asc -name ImportedKey +``` + +You will be asked to decrypt the encrypted private key armor on disk (if it is encrypted, if not, use the `--unsafe` +flag), and then to provide an encryption password for storing the key in the keystore. + +After executing the previous command, the `gnokey` keystore will have imported `ImportedKey`. + +![gnokey import](../assets/getting-started/creating-a-key-pair/gnokey-import.gif) diff --git a/docs/go-gno-compatibility.md b/docs/go-gno-compatibility.md deleted file mode 120000 index a15871f0340..00000000000 --- a/docs/go-gno-compatibility.md +++ /dev/null @@ -1 +0,0 @@ -../gnovm/docs/go-gno-compatibility.md \ No newline at end of file diff --git a/docs/how-to-guides/connect-wallet-dapp.md b/docs/how-to-guides/connect-wallet-dapp.md new file mode 100644 index 00000000000..294323b5560 --- /dev/null +++ b/docs/how-to-guides/connect-wallet-dapp.md @@ -0,0 +1,115 @@ +--- +id: connect-wallet-dapp +--- + +# How to connect a wallet to a dApp + +As a dapp developer, you must integrate a web3 wallet with your application to enable users to interact with your +application. Upon integration, you may retrieve account information of the connected user or request to sign & send +transactions from the user's account. + +:::warning Wallets on gno.land + +Here is a list of available wallets for Gnoland. +Note that none of these wallets are official or exclusive, so please +use them at your own diligence: + +- [Adena Wallet](https://adena.app/) + +::: + +## Adena Wallet + +[Adena](https://adena.app/) is a web extension wallet that supports the Gnoland blockchain. Below is the basic Adena +APIs that you can use for your application. For more detailed information, check out +Adena's [developer's docs](https://docs.adena.app/) to integrate Adena to your application. + +### Adena Connect For React App + +Check if Adena wallet exists. + +```javascript +// checks the existence of the adena object in window + +const existsWallet = () => { + if (window.adena) { + return true; + } + return false; +}; + +``` + +Register your website as a trusted domain. + +```javascript +// calls the AddEstablish of the adena object + +const addEstablish = (siteName) => { + return window?.adena?.AddEstablish(siteName); +}; + +``` + +Retrieve information about the connected account. + +```javascript +// calls the GetAccount function of the adena object + +const getAccount = () => { + return window.adena?.GetAccount(); +}; + +``` + +Request approval of a transaction that transfers tokens. + +```javascript +// Execute the DoContract function of the adena object to request transaction. + +const sendToken = (fromAddress, toAddress, sendAmount) => { + const message = { + type: "/bank.MsgSend", + value: { + from_address: fromAddress, + to_address: toAddress, + amount: sendAmount + } + }; + + return window.adena?.DoContract({ + messages: [message], + gasFee: 1, + gasWanted: 3000000 + }); +}; + +``` + +Request approval of a transaction that calls a function from a realm. + +```javascript +// Execute the DoContract function of the adena object to request transaction. + +const doContractPackageFunction = (caller, func, pkgPath, argument) => { + + // Setup Transaction Message + const message = { + type: "/vm.m_call", + value: { + caller, + func, + send: "", + pkg_path: pkgPath, + args: argument.split(',') + } + }; + + // Request Transaction + return window.adena?.DoContract({ + messages: [message], + gasFee: 1, + gasWanted: 3000000 + }); +}; +``` diff --git a/docs/how-to-guides/creating-grc20.md b/docs/how-to-guides/creating-grc20.md new file mode 100644 index 00000000000..d91499afb39 --- /dev/null +++ b/docs/how-to-guides/creating-grc20.md @@ -0,0 +1,164 @@ +--- +id: creating-grc20 +--- + +# How to create a GRC20 Token + +## Overview + +This guide shows you how to write a simple _GRC20_ Smart Contract, or rather a [Realm](../explanation/realms.md), in [Gno (Gnolang)](../explanation/gno-language.md). For actually deploying the Realm, please see the [deployment](deploy.md) guide. + +Our _GRC20_ Realm will have the following functionality: + +- Minting a configurable amount of token. +- Keeping track of total token supply. +- Fetching the balance of an account. + +## Prerequisites + +We will proceed using the typical directory structure for a Realm found within the [simple-contract guide](simple-contract.md). It is also worthwhile to consult the [GRC20 interface](../../examples/gno.land/p/demo/grc/grc20/igrc20.gno) which we will be importing and utilizing within this guide. + +## 1. Importing token package +For this realm, we'll want to import the `grc20` package as this will include the main functionality of our token factory realm. + +[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-1.gno go) +```go +package mytoken + +import ( + "std" + + "gno.land/p/demo/grc/grc20" +) + +var ( + mytoken *grc20.AdminToken + admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // set admin account +) + +// init is a constructor function that runs only once (at time of deployment) +func init() { + // provision the token's name, symbol and number of decimals + mytoken = grc20.NewAdminToken("Mytoken", "MTKN", 4) + + // set the total supply + mytoken.Mint(admin, 1000000*10000) // @administrator (supply = 1 million) +} +``` + +In this code preview, we have: +- Defined a new local variable `mytoken` and assigned that the type of pointer to `grc20.AdminToken`. +- Defined and set the value of local variable `admin` to point to a specific gno.land address of type `std.Address`. +- Set the value of `mytoken` (type `*AdminToken`) to equal the result of creating a new token and configuring its name, symbol + decimal representation. +- Minted 1 million `Mytoken` and set the administrator as the owner of these tokens. + +## 2. Adding token functionality + +The following section will be about introducing Public functions to expose functionality imported from the [grc20 package](../../examples/gno.land/p/demo/grc/grc20). + +[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go) +```go +func TotalSupply() uint64 { + return mytoken.TotalSupply() +} + +func BalanceOf(owner users.AddressOrName) uint64 { + balance, err := mytoken.BalanceOf(owner.Resolve()) + if err != nil { + panic(err) + } + return balance +} + +func Allowance(owner, spender users.AddressOrName) uint64 { + allowance, err := mytoken.Allowance(owner.Resolve(), spender.Resolve()) + if err != nil { + panic(err) + } + return allowance +} + +func Transfer(to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := mytoken.Transfer(caller, to.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Approve(spender users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := mytoken.Approve(caller, spender.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func TransferFrom(from, to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := mytoken.TransferFrom(caller, from.Resolve(), to.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Mint(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mytoken.Mint(address.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Burn(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mytoken.Burn(address.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return mytoken.RenderHome() + case c == 2 && parts[0] == "balance": + owner := users.AddressOrName(parts[1]) + balance, _ := mytoken.BalanceOf(owner.Resolve()) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} +``` + +Detailing what is happening in the above code: +- Calling the `TotalSupply` method would return the total number of tokens minted. +- Calling the `BalanceOf` method would return the total balance of an account. +- Calling the `Allowance` method would set an account as an allowed spender to serve on behalf of the owner. +- Calling the `transfer` method would transfer a configurable amount of token from the calling account to another account, either owned or unowned. +- Calling the `Approve` method would approve a calling account to spend a configurable amount of token on behalf of the token owner. +- Calling the `TransferFrom` method would transfer a configurable amount of token from an account that granted approval to another account, either owned or unowned. +- Calling the `Mint` method would create a configurable number of tokens by the administrator. +- Calling the `Burn` method would destroy a configurable number of tokens by the administrator. +- Calling the `Render` method would return a user's `balance` as a formatted string. Learn more about the `Render` + method and how it's used [here](../explanation/realms.md). +- Finally, we provide a local function to assert that the calling account is in fact the owner, otherwise panic. This is a very important function that serves to prevent abuse by non-administrators. + +## Conclusion + +That's it 🎉 + +You have successfully built a simple GRC20 Realm that is ready to be deployed on the Gno chain and called by users. +In the upcoming guides, we will see how we can develop more complex Realm logic and have them interact with outside tools like a wallet application. diff --git a/docs/how-to-guides/creating-grc721.md b/docs/how-to-guides/creating-grc721.md new file mode 100644 index 00000000000..afaa2da0ef3 --- /dev/null +++ b/docs/how-to-guides/creating-grc721.md @@ -0,0 +1,199 @@ +--- +id: creating-grc721 +--- + +# How to create a GRC721 Token (NFT) + +## Overview + +This guide shows you how to write a simple _GRC721_ Smart Contract, or rather a [Realm](../explanation/realms.md), +in [Gno (Gnolang)](../explanation/gno-language.md). For actually deploying the Realm, please see +the [deployment](deploy.md) guide. + +Our _GRC721_ Realm will have the following functionality: + +- Minting a configurable amount of token. +- Keeping track of total token supply. +- Fetching the balance of an account. + +## Prerequisites + +We will proceed using the typical directory structure for a Realm found within +the [simple-contract guide](simple-contract.md). It is also worthwhile to consult +the [GRC721 interface](../../examples/gno.land/p/demo/grc/grc721/igrc721.gno) which we will be borrowing from within +this guide. + +## 1. Importing token package + +For this realm, we'll want to import the `grc20` package as this will include the main functionality of our token +factory realm. + +[embedmd]:# (../assets/how-to-guides/creating-grc721/mynonfungibletoken-1.gno go) +```go +package mynonfungibletoken + +import ( + "std" + + "gno.land/p/demo/grc/grc721" +) + +var ( + admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // set admin account + // provision the token's name and symbol + mynonfungibletoken = grc721.NewBasicNFT("mynonfungibletoken", "MNFT") +) + +func init() { + mintNNFT(admin, 10) // @administrator (supply = 10) +} +``` + +In this code preview, we have: + +- Defined and set the value of `mynonfungibletoken` (type `*grc721.basicNFT`) to equal the result of creating a new + token and configuring its name and symbol. +- Defined and set the value of local variable `admin` to point to a specific gno.land address of type `std.Address`. +- Minted 5 `mynonfungibletoken (MNFT)` and set the administrator as the owner of these tokens + +## 2. Adding token functionality + +The following section will be about introducing Public functions to expose functionality imported from +the [grc721 package](../../examples/gno.land/p/demo/grc/grc721). + +[embedmd]:# (../assets/how-to-guides/creating-grc721/mynonfungibletoken-2.gno go) +```go +func mintNNFT(owner std.Address, n uint64) { + count := my.TokenCount() + for i := count; i < count+n; i++ { + tid := grc721.TokenID(ufmt.Sprintf("%d", i)) + mynonfungibletoken.Mint(owner, tid) + } +} + +// Getters + +func BalanceOf(user users.AddressOrName) uint64 { + balance, err := mynonfungibletoken.BalanceOf(user.Resolve()) + if err != nil { + panic(err) + } + + return balance +} + +func OwnerOf(tid grc721.TokenID) std.Address { + owner, err := mynonfungibletoken.OwnerOf(tid) + if err != nil { + panic(err) + } + + return owner +} + +func IsApprovedForAll(owner, user users.AddressOrName) bool { + return mynonfungibletoken.IsApprovedForAll(owner.Resolve(), user.Resolve()) +} + +func GetApproved(tid grc721.TokenID) std.Address { + addr, err := mynonfungibletoken.GetApproved(tid) + if err != nil { + panic(err) + } + + return addr +} + +// Setters + +func Approve(user users.AddressOrName, tid grc721.TokenID) { + err := mynonfungibletoken.Approve(user.Resolve(), tid) + if err != nil { + panic(err) + } +} + +func SetApprovalForAll(user users.AddressOrName, approved bool) { + err := mynonfungibletoken.SetApprovalForAll(user.Resolve(), approved) + if err != nil { + panic(err) + } +} + +func TransferFrom(from, to users.AddressOrName, tid grc721.TokenID) { + err := mynonfungibletoken.TransferFrom(from.Resolve(), to.Resolve(), tid) + if err != nil { + panic(err) + } +} + +// Admin + +func Mint(to users.AddressOrName, tid grc721.TokenID) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mynonfungibletoken.Mint(to.Resolve(), tid) + if err != nil { + panic(err) + } +} + +func Burn(tid grc721.TokenID) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mynonfungibletoken.Burn(tid) + if err != nil { + panic(err) + } +} + +// Render + +func Render(path string) string { + switch { + case path == "": + return mynonfungibletoken.RenderHome() + default: + return "404\n" + } +} + +// Util + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} +``` + +Detailing what is happening in the above code: + +- Calling the **local** `mintNNFT` method would mint a configurable number of tokens to the provided owner's account. +- Calling the `BalanceOf` method would return the total balance of an account. +- Calling the `OwnerOf` method would return the owner of the token based on the ID that is passed into the method. +- Calling the `IsApprovedByAll` method will return true if an operator is approved for all operations by the owner; + otherwise, returns false. +- Calling the `GetApproved` method will return the address approved to operate on the token. +- Calling the `Approve` method would approve the input address for a particular token. +- Calling the `SetApprovalForAll` method would approve an operating account to operate on all tokens. +- Calling the `TransferFrom` method would transfer a configurable amount of token from an account that granted approval + to another account, either owned or unowned. +- Calling the `Mint` method would create a configurable number of tokens by the administrator. +- Calling the `Burn` method would destroy a configurable number of tokens by the administrator. +- Calling the `Render` method on success would invoke + a [`RenderHome`](https://github.com/gnolang/gno/blob/master/examples/gno.land/p/demo/grc/grc721/basic_nft.gno#L353) + method on the `grc721` instance we instantiated at the top of the file; this method returns a formatted string that + includes the token: symbol, supply and account balances (`balances avl.Tree`) which is a mapping denoted + as: `OwnerAddress -> TokenCount`; otherwise returns false and renders a `404`; you can find more information about + this `Render` method and how it's used [here](../explanation/realms.md). +- Finally, we provide a local function to assert that the calling account is in fact the owner, otherwise panic. This is + a very important function that serves to prevent abuse by non-administrators. + +## Conclusion + +That's it 🎉 + +You have successfully built a simple GRC721 Realm that is ready to be deployed on the Gno chain and called by users. +In the upcoming guides, we will see how we can develop more complex Realm logic and have them interact with outside +tools like a wallet application. diff --git a/docs/how-to-guides/deploy.md b/docs/how-to-guides/deploy.md new file mode 100644 index 00000000000..ab31716f014 --- /dev/null +++ b/docs/how-to-guides/deploy.md @@ -0,0 +1,74 @@ +--- +id: deploy +--- + +# How to deploy a Realm / Package + +## Overview + +This guide shows you how to deploy any realm or package to the Gno chain. Deployment is be done by utilizing `gnokey`'s `maketx addpkg` API. + +:::info +Regardless of whether you're deploying a realm or a package, you will be using `gnokey`'s `maketx addpkg` - the usage of `maketx addpkg` in both cases is identical. +::: + +## Prerequisites + +- **Have `gnokey` installed** +- **Have access to a `gnoland` node (local or remote)** +- **Have generated a keypair with `gnokey` & funded it with `gnot`** +- **Have a Realm or Package ready to deploy** + +## Deploying + +To illustrate deployment, we will use a realm. Consider the following folder structure: + +``` +counter-app/ +├─ r/ +│ ├─ counter/ +│ │ ├─ counter.gno +``` + +We would like to deploy the realm found in `counter.gno`. To do this, open a terminal at `counter-app/` and use the following `gnokey` command: + +```bash +gnokey maketx addpkg \ +--pkgpath "gno.land/r/demo/counter" \ +--pkgdir "./r/counter" \ +--gas-fee 10000000ugnot \ +--gas-wanted 800000 \ +--broadcast \ +--chainid dev \ +--remote localhost:26657 \ +MyKey +``` + +Let's analyze all of the flags in detail: +- `--pkgpath` - path where the package/realm will be placed on-chain +- `--pkgdir` - local path where the package/realm is located +- `--gas-wanted` - the upper limit for units of gas for the execution of the transaction - similar to Solidity's gas limit +- `--gas-fee` - similar to Solidity's gas-price +- `--broadcast` - broadcast the transaction on-chain +- `--chain-id` - id of the chain to connect to - local or remote +- `--remote` - `gnoland` node endpoint - local or remote +- `MyKey` - the keypair to use for the transaction + +:::info +As of October 2023, `--gas-fee` is fixed to 1gnot (10000000ugnot), with plans to change it down the line. +::: + +Next, confirm the transaction with your keypair passphrase. If deployment was successful, you will be presented with a message similar to the following: + +``` +OK! +GAS WANTED: 800000 +GAS USED: 775097 +``` +Depending on the size of the package/realm, you might need to increase amount given in the `--gas-wanted` flag to cover the deployment cost. + +## Conclusion + +That's it 🎉 + +You have now successfully deployed a realm/package to a Gno.land chain. diff --git a/docs/how-to-guides/interact-with-gnoland.md b/docs/how-to-guides/interact-with-gnoland.md new file mode 100644 index 00000000000..dacfead2957 --- /dev/null +++ b/docs/how-to-guides/interact-with-gnoland.md @@ -0,0 +1,94 @@ +--- +id: interact-with-gnoland +--- + +# Interact with Gno.land + +This tutorial will teach you how to interact with the gno.land blockchain by creating an account and calling various realms to send transactions on the network. + +## Prerequisites + +- [Installation](../getting-started/local-setup.md) + +## Create an Account + +In order to interact with Gnoland, you need an account that you will use to sign and send transactions. You may create a new account with `gnokey generate` or recover an existing one with `gnokey add`. Confirm that your account was successfully added with `gnokey list` to display all accounts registered in the key base of your device. + +```bash +gnokey generate # create a new seed phrase (mnemonic) + +gnokey add -recover {your_account_name} # registers a key with the name set as the value you put in {your_account_name} with a seed phrase + +gnokey list # check the list of keys +``` + +## Register As a User + +```bash +gnokey maketx call \ + -gas-fee="1ugnot" \ + -gas-wanted="5000000" \ + -broadcast="true" \ + -remote="staging.gno.land:36657" \ + -chainid="test3" \ + -pkgpath="gno.land/r/demo/users" \ + -func="Register" \ + -args="" \ + -args="my_account" \ # (must be at least 6 characters, lowercase alphanumeric with underscore) + -args="" \ + -send="200000000ugnot" \ + my-account + +# username: must be at least 6 characters, lowercase alphanumeric with underscore +``` + +> **Note:** With a user registration fee of 200 GNOT and a gas fee that ranges up to 2 GNOT, you must have around 202 GNOT to complete this transaction. After registering as a user, you may replace your address with your `username` when developing or publishing a realm package. + +## Get Account Information + +```bash +# Get account information +gnokey query -remote="staging.gno.land:36657" "auth/accounts/{address}" + +# Get account balance +gnokey query -remote="staging.gno.land:36657" "bank/balances/{address}" + +# Get /r/demo/boards user information +gnokey query -remote="staging.gno.land:36657" -data "gno.land/r/demo/users +my_account" "vm/qrender" +``` + +## Send Tokens + +The following command will send 1,000,000 ugnot (= 1 GNOT) to the address specified in the `to` argument. + +```bash +# Creates and broadcast a token transfer transaction +gnokey maketx send \ + -gas-fee="1ugnot" \ + -gas-wanted="5000000" \ + -broadcast="true" \ + -remote="staging.gno.land:36657" \ + -chainid="test3" \ + -to="{address}" \ # g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 + -send="{amount}{denom}" \ # 1234ugnot + my-account +``` + +## Create a Board + +Try creating a board called `my_board` on the `gno.land/r/demo/boards` realm with the following command: + +```bash +# Calls the CreateBoard function of gno.land/r/demo/boards +gnokey maketx call \ + -gas-fee="1ugnot" \ + -gas-wanted="5000000" \ + -broadcast="true" \ + -remote "staging.gno.land:36657" \ + -chainid="test3" \ + -pkgpath="gno.land/r/demo/boards" \ + -func="CreateBoard" \ + -args="my_board" \ + my-account +``` diff --git a/docs/how-to-guides/porting-solidity-to-gno.md b/docs/how-to-guides/porting-solidity-to-gno.md new file mode 100644 index 00000000000..39c6910eda6 --- /dev/null +++ b/docs/how-to-guides/porting-solidity-to-gno.md @@ -0,0 +1,680 @@ +--- +id: port-solidity-to-gno +--- + +# Port a Solidity Contract to a Gnolang Realm + + +## Overview + +This guide shows you how to port a Solidity contract `Simple Auction` to a Gnolang Realm `auction.gno` with test cases (Test Driven Development (TDD) approach). + +You can check the Solidity contract in this [link](https://docs.soliditylang.org/en/latest/solidity-by-example.html#simple-open-auction), and here's the code for porting. + +```solidity +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.4; +contract SimpleAuction { + // Parameters of the auction. Times are either + // absolute unix timestamps (seconds since 1970-01-01) + // or time periods in seconds. + address payable public beneficiary; + uint public auctionEndTime; + + // Current state of the auction. + address public highestBidder; + uint public highestBid; + + // Allowed withdrawals of previous bids + mapping(address => uint) pendingReturns; + + // Set to true at the end, disallows any change. + // By default initialized to `false`. + bool ended; + + // Events that will be emitted on changes. + event HighestBidIncreased(address bidder, uint amount); + event AuctionEnded(address winner, uint amount); + + // Errors that describe failures. + + // The triple-slash comments are so-called natspec + // comments. They will be shown when the user + // is asked to confirm a transaction or + // when an error is displayed. + + /// The auction has already ended. + error AuctionAlreadyEnded(); + /// There is already a higher or equal bid. + error BidNotHighEnough(uint highestBid); + /// The auction has not ended yet. + error AuctionNotYetEnded(); + /// The function auctionEnd has already been called. + error AuctionEndAlreadyCalled(); + + /// Create a simple auction with `biddingTime` + /// seconds bidding time on behalf of the + /// beneficiary address `beneficiaryAddress`. + constructor( + uint biddingTime, + address payable beneficiaryAddress + ) { + beneficiary = beneficiaryAddress; + auctionEndTime = block.timestamp + biddingTime; + } + + /// Bid on the auction with the value sent + /// together with this transaction. + /// The value will only be refunded if the + /// auction is not won. + function bid() external payable { + // No arguments are necessary, all + // information is already part of + // the transaction. The keyword payable + // is required for the function to + // be able to receive Ether. + + // Revert the call if the bidding + // period is over. + if (block.timestamp > auctionEndTime) + revert AuctionAlreadyEnded(); + + // If the bid is not higher, send the + // money back (the revert statement + // will revert all changes in this + // function execution including + // it having received the money). + if (msg.value <= highestBid) + revert BidNotHighEnough(highestBid); + + if (highestBid != 0) { + // Sending back the money by simply using + // highestBidder.send(highestBid) is a security risk + // because it could execute an untrusted contract. + // It is always safer to let the recipients + // withdraw their money themselves. + pendingReturns[highestBidder] += highestBid; + } + highestBidder = msg.sender; + highestBid = msg.value; + emit HighestBidIncreased(msg.sender, msg.value); + } + + /// Withdraw a bid that was overbid. + function withdraw() external returns (bool) { + uint amount = pendingReturns[msg.sender]; + if (amount > 0) { + // It is important to set this to zero because the recipient + // can call this function again as part of the receiving call + // before `send` returns. + pendingReturns[msg.sender] = 0; + + // msg.sender is not of type `address payable` and must be + // explicitly converted using `payable(msg.sender)` in order + // use the member function `send()`. + if (!payable(msg.sender).send(amount)) { + // No need to call throw here, just reset the amount owing + pendingReturns[msg.sender] = amount; + return false; + } + } + return true; + } + + /// End the auction and send the highest bid + /// to the beneficiary. + function auctionEnd() external { + // It is a good guideline to structure functions that interact + // with other contracts (i.e. they call functions or send Ether) + // into three phases: + // 1. checking conditions + // 2. performing actions (potentially changing conditions) + // 3. interacting with other contracts + // If these phases are mixed up, the other contract could call + // back into the current contract and modify the state or cause + // effects (ether payout) to be performed multiple times. + // If functions called internally include interaction with external + // contracts, they also have to be considered interaction with + // external contracts. + + // 1. Conditions + if (block.timestamp < auctionEndTime) + revert AuctionNotYetEnded(); + if (ended) + revert AuctionEndAlreadyCalled(); + + // 2. Effects + ended = true; + emit AuctionEnded(highestBidder, highestBid); + + // 3. Interaction + beneficiary.transfer(highestBid); + } +} +``` + +These are the basic concepts of the Simple Auction contract: + +* Everyone can send their bids during a bidding period. +* The bids already include sending money / Ether in order to bind the bidders to their bids. +* If the highest bid is raised, the previous highest bidder gets their money back. +* After the end of the bidding period, the contract has to be called manually for the beneficiary to receive their money - contracts cannot activate themselves. + +The contract consists of: + +* A variable declaration +* Initialization by a constructor +* Three functions + +Let's dive into the details of the role of each function, and learn how to port each function into Gnolang with test cases. + +When writing a test case, the following conditions are often used to determine whether the function has been properly executed: + +* Value matching +* Error status +* Panic status + +Below is a test case helper that will help implement each condition. + +### Gnolang - Testcase Helper + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-1.gno go) +```go +func shouldEqual(t *testing.T, got interface{}, expected interface{}) { + t.Helper() + + if got != expected { + t.Errorf("expected %v(%T), got %v(%T)", expected, expected, got, got) + } +} + +func shouldErr(t *testing.T, err error) { + t.Helper() + if err == nil { + t.Errorf("expected an error, but got nil.") + } +} + +func shouldNoErr(t *testing.T, err error) { + t.Helper() + if err != nil { + t.Errorf("expected no error, but got err: %s.", err.Error()) + } +} + +func shouldPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("should have panic") + } + }() + f() +} + +func shouldNoPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r != nil { + t.Errorf("should not have panic") + } + }() + f() +} +``` + +## Variable init - Solidity + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-2.sol solidity) +```solidity +// Parameters of the auction. Times are either +// absolute unix timestamps (seconds since 1970-01-01) +// or time periods in seconds. +address payable public beneficiary; +uint public auctionEndTime; + +// Current state of the auction. +address public highestBidder; +uint public highestBid; + +// Allowed withdrawals of previous bids +mapping(address => uint) pendingReturns; + +// Set to true at the end, disallows any change. +// By default initialized to `false`. +bool ended; + +// Events that will be emitted on changes. +event HighestBidIncreased(address bidder, uint amount); +event AuctionEnded(address winner, uint amount); + +// Errors that describe failures. + +// The triple-slash comments are so-called natspec +// comments. They will be shown when the user +// is asked to confirm a transaction or +// when an error is displayed. + +/// The auction has already ended. +error AuctionAlreadyEnded(); +/// There is already a higher or equal bid. +error BidNotHighEnough(uint highestBid); +/// The auction has not ended yet. +error AuctionNotYetEnded(); +/// The function auctionEnd has already been called. +error AuctionEndAlreadyCalled(); + +/// Create a simple auction with `biddingTime` +/// seconds bidding time on behalf of the +/// beneficiary address `beneficiaryAddress`. +constructor( + uint biddingTime, + address payable beneficiaryAddress +) { + beneficiary = beneficiaryAddress; + auctionEndTime = block.timestamp + biddingTime; +} +``` + +* `address payable public beneficiary;` : Address to receive the amount after the auction's ending. +* `uint public auctionEndTime;` : Auction ending time. +* `address public highestBidder;` : The highest bidder. +* `uint public highestBid;` : The highest bid. +* `mapping(address => uint) pendingReturns;` : Bidder's address and amount to be returned (in case of the highest bid changes). +* `bool ended;` : Whether the auction is closed. + +### Variable init - Gnolang + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-3.gno go) +```go +var ( + receiver = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + auctionEndBlock = std.GetHeight() + uint(300) // in blocks + highestBidder std.Address + highestBid = uint(0) + pendingReturns avl.Tree + ended = false +) +``` + +> **Note:** In Solidity, the Auction ending time is set by a time basis, but in the above case, it's set by a block basis. + +### + +## bid() - Solidity + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-4.sol solidity) +```solidity +function bid() external payable { + // No arguments are necessary, all + // information is already part of + // the transaction. The keyword payable + // is required for the function to + // be able to receive Ether. + + // Revert the call if the bidding + // period is over. + if (block.timestamp > auctionEndTime) + revert AuctionAlreadyEnded(); + + // If the bid is not higher, send the + // money back (the revert statement + // will revert all changes in this + // function execution including + // it having received the money). + if (msg.value <= highestBid) + revert BidNotHighEnough(highestBid); + + if (highestBid != 0) { + // Sending back the money by simply using + // highestBidder.send(highestBid) is a security risk + // because it could execute an untrusted contract. + // It is always safer to let the recipients + // withdraw their money themselves. + pendingReturns[highestBidder] += highestBid; + } + highestBidder = msg.sender; + highestBid = msg.value; + emit HighestBidIncreased(msg.sender, msg.value); +} +``` + +`bid()` function is for participating in an auction and includes: + +* Determining whether an auction is closed. +* Comparing a new bid with the current highest bid. +* Prepare data to return the bid amount to the existing highest bidder in case of the highest bid is increased. +* Update variables with the top bidder & top bid amount. + +### bid() - Gnolang + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-5.gno go) +```go +func Bid() { + if std.GetHeight() > auctionEndBlock { + panic("Exceeded auction end block") + } + + sentCoins := std.GetOrigSend() + if len(sentCoins) != 1 { + panic("Send only one type of coin") + } + + sentAmount := uint(sentCoins[0].Amount) + if sentAmount <= highestBid { + panic("Too few coins sent") + } + + // A new bid is higher than the current highest bid + if sentAmount > highestBid { + // If the highest bid is greater than 0, + if highestBid > 0 { + // Need to return the bid amount to the existing highest bidder + // Create an AVL tree and save + pendingReturns.Set(highestBidder.String(), highestBid) + } + + // Update the top bidder address + highestBidder = std.GetOrigCaller() + // Update the top bid amount + highestBid = sentAmount + } +} +``` + +### bid() - Gnolang Testcase + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-6.gno go) +```go +// 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) +} +``` + +### + +## withdraw() - Solidity + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-7.sol solidity) +```solidity +/// Withdraw a bid that was overbid. +function withdraw() external returns (bool) { + uint amount = pendingReturns[msg.sender]; + if (amount > 0) { + // It is important to set this to zero because the recipient + // can call this function again as part of the receiving call + // before `send` returns. + pendingReturns[msg.sender] = 0; + + // msg.sender is not of type `address payable` and must be + // explicitly converted using `payable(msg.sender)` in order + // use the member function `send()`. + if (!payable(msg.sender).send(amount)) { + // No need to call throw here, just reset the amount owing + pendingReturns[msg.sender] = amount; + return false; + } + } + return true; +} +``` + +`withdraw()` is to return the bid amount to the existing highest bidder in case of the highest bid changes and includes: + +* When called, determine if there's a bid amount to be returned to the address. +* (If there's an amount to be returned) Before returning, set the previously recorded amount to `0` and return the actual amount. + +### withdraw() - Gnolang + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-8.gno go) +```go +func Withdraw() { + // Query the return amount to non-highest bidders + amount, _ := pendingReturns.Get(std.GetOrigCaller().String()) + + if amount > 0 { + // If there's an amount, reset the amount first, + pendingReturns.Set(std.GetOrigCaller().String(), 0) + + // Return the exceeded amount + banker := std.GetBanker(std.BankerTypeRealmSend) + pkgAddr := std.GetOrigPkgAddr() + + banker.SendCoins(pkgAddr, std.GetOrigCaller(), std.Coins{{"ugnot", amount.(int64)}}) + } +} +``` + +### + +### withdraw() - Gnolang Testcase + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-9.gno go) +```go +// Withdraw Function Test +func TestWithdraw(t *testing.T) { + // If there's no participants for return + shouldEqual(t, pendingReturns.Size(), 0) + + // If there's participants for return (data generation + returnAddr := bidder01.String() + returnAmount := int64(3) + pendingReturns.Set(returnAddr, returnAmount) + shouldEqual(t, pendingReturns.Size(), 1) + shouldEqual(t, pendingReturns.Has(returnAddr), true) + + banker := std.GetBanker(std.BankerTypeRealmSend) + pkgAddr := std.GetOrigPkgAddr() + banker.SendCoins(pkgAddr, std.Address(returnAddr), std.Coins{{"ugnot", returnAmount}}) + shouldEqual(t, banker.GetCoins(std.Address(returnAddr)).String(), "3ugnot") +} +``` + +## auctionEnd() - Solidity + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-10.sol solidity) +```solidity +/// End the auction and send the highest bid +/// to the beneficiary. +function auctionEnd() external { + // It is a good guideline to structure functions that interact + // with other contracts (i.e. they call functions or send Ether) + // into three phases: + // 1. checking conditions + // 2. performing actions (potentially changing conditions) + // 3. interacting with other contracts + // If these phases are mixed up, the other contract could call + // back into the current contract and modify the state or cause + // effects (ether payout) to be performed multiple times. + // If functions called internally include interaction with external + // contracts, they also have to be considered interaction with + // external contracts. + + // 1. Conditions + if (block.timestamp < auctionEndTime) + revert AuctionNotYetEnded(); + if (ended) + revert AuctionEndAlreadyCalled(); + + // 2. Effects + ended = true; + emit AuctionEnded(highestBidder, highestBid); + + // 3. Interaction + beneficiary.transfer(highestBid); +} +``` + +`auctionEnd()` function is for ending the auction and includes: + +* Determines if the auction should end by comparing the end time. +* Determines if the auction has already ended or not. + * (If not ended) End the auction. + * (If not ended) Send the highest bid amount to the recipient. + +### auctionEnd() - Gnolang + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-11.gno go) +```go +func AuctionEnd() { + if std.GetHeight() < auctionEndBlock { + panic("Auction hasn't ended") + } + + if ended { + panic("Auction has ended") + + } + ended = true + + // Send the highest bid to the recipient + banker := std.GetBanker(std.BankerTypeRealmSend) + pkgAddr := std.GetOrigPkgAddr() + + banker.SendCoins(pkgAddr, receiver, std.Coins{{"ugnot", int64(highestBid)}}) +} +``` + +### auctionEnd() - Gnolang Testcase + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-12.gno go) +```go +// AuctionEnd() Function Test +func TestAuctionEnd(t *testing.T) { + // Auction is ongoing + shouldPanic(t, AuctionEnd) + + // Auction ends + highestBid = 3 + std.TestSkipHeights(500) + shouldNoPanic(t, AuctionEnd) + shouldEqual(t, ended, true) + + banker := std.GetBanker(std.BankerTypeRealmSend) + shouldEqual(t, banker.GetCoins(receiver).String(), "3ugnot") + + // Auction has already ended + shouldPanic(t, AuctionEnd) + shouldEqual(t, ended, true) +} +``` + +## Precautions for Running Test Cases + +* Each test function should be executed separately one by one, to return all passes without any errors. +* Same as Golang, Gnolang doesn't support `setup()` & `teardown()` functions. So running two or more test functions simultaneously can result in tainted data. +* If you want to do the whole test at once, make it into a single function as below: + +[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-13.gno go) +```go +// The whole test +func TestFull(t *testing.T) { + bidder01 := testutils.TestAddress("bidder01") // g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw + bidder02 := testutils.TestAddress("bidder02") // g1vf5kger9wgcryh6lta047h6lta047h6lnhe2x2 + + // Variables test + { + shouldEqual(t, highestBidder, "") + shouldEqual(t, receiver, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + shouldEqual(t, auctionEndBlock, 423) + shouldEqual(t, highestBid, 0) + shouldEqual(t, pendingReturns.Size(), 0) + shouldEqual(t, ended, false) + } + + // Send two or more types of coins + { + std.TestSetOrigCaller(bidder01) + std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) + shouldPanic(t, Bid) + } + + // Send less than the highest bid + { + std.TestSetOrigCaller(bidder01) + std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) + shouldPanic(t, Bid) + } + + // Send more than the highest bid + { + std.TestSetOrigCaller(bidder01) + std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + shouldNoPanic(t, Bid) + + shouldEqual(t, pendingReturns.Size(), 0) + shouldEqual(t, highestBid, 1) + shouldEqual(t, highestBidder, "g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw") + } + + // Other participants in the auction + { + + // Send less amount than the current highest bid (current: 1) + std.TestSetOrigCaller(bidder02) + std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) + shouldPanic(t, Bid) + + // Send more amount than the current highest bid (exceeded) + std.TestSetOrigCaller(bidder02) + std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) + shouldNoPanic(t, Bid) + + shouldEqual(t, highestBid, 2) + shouldEqual(t, highestBidder, "g1vf5kger9wgcryh6lta047h6lta047h6lnhe2x2") + + shouldEqual(t, pendingReturns.Size(), 1) // Return to the existing bidder + shouldEqual(t, pendingReturns.Has("g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw"), true) + } + + // Auction ends + { + std.TestSkipHeights(150) + shouldPanic(t, AuctionEnd) + shouldEqual(t, ended, false) + + std.TestSkipHeights(301) + shouldNoPanic(t, AuctionEnd) + shouldEqual(t, ended, true) + + banker := std.GetBanker(std.BankerTypeRealmSend) + shouldEqual(t, banker.GetCoins(receiver).String(), "2ugnot") + } +} +``` diff --git a/docs/how-to-guides/simple-contract.md b/docs/how-to-guides/simple-contract.md new file mode 100644 index 00000000000..0e77a6a75e1 --- /dev/null +++ b/docs/how-to-guides/simple-contract.md @@ -0,0 +1,149 @@ +--- +id: simple-contract +--- + +# How to write a simple Gno Smart Contract (Realm) + +## Overview + +This guide shows you how to write a simple _Counter_ Smart Contract, or rather a [Realm](../explanation/realms.md), +in [Gno (Gnolang)](../explanation/gno-language.md). For actually deploying the Realm, please see +the [deployment](deploy.md) guide. + +Our _Counter_ Realm will have the following functionality: + +- Keeping track of the current count. +- Incrementing / decrementing the count. +- Fetching the current count value. + +## Prerequisites + +- **Text editor** + +:::info Editor support +The Gno language is based on Golang, but it does not have all the bells and whistles in major text editors like Go. +Advanced language features like IntelliSense are still in the works. + +Currently, we officially have language support +for [ViM](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md#vim-support), +[Emacs](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md#emacs-support) +and [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=harry-hov.gno). +::: + +## 1. Setting up the work directory + +Gno Realms can be typically written anywhere, under any structure, just like regular Go code. +However, Gno developers have adopted a standard of organizing Gno logic under a specific directory hierarchy, which we +will explore here. + +Create the main working directory for our Realm: + +```bash +mkdir counter-app +``` + +Since we are building a simple _Counter_ Realm, inside our created `counter-app` directory, we can create another +directory named `r`, which stands for `realm`: + +```bash +cd counter-app +mkdir r +``` + +Alternatively, if we were writing a [Gno Package](../explanation/packages.md), we would denote this directory name +as `p` (for `package`). You can learn more about Packages in our [Package development guide](simple-library.md). + +Additionally, we will create another sub-folder that will house our Realm code, named `counter`: + +```bash +cd r +mkdir counter +``` + +After setting up our work directory structure, we should have something like this: + +```text +counter-app/ +├─ r/ +│ ├─ counter/ +│ │ ├─ // source code here +``` + +## 2. Create `counter.gno` + +Now that the work directory structure is set up, we can go into the `counter` sub-folder, and actually create +our _Counter_ Smart Contract: + +```bash +cd counter +touch counter.gno +``` + +:::info Gno file extension +All Gno (Gnolang) source code has the file extension `.gno`. + +This file extension is required for existing gno tools and processes to work. +::: + +We can finally write out the logic of the _Counter_ Smart Contract in `counter.gno`: + +[embedmd]:# (../assets/how-to-guides/simple-contract/counter.gno go) +```go +package counter + +import "fmt" + +var count int + +func Increment() { + count++ +} + +func Decrement() { + count-- +} + +func Render(_ string) string { + return fmt.Sprintf("Count: %d", count) +} +``` + +There are a few things happening here, so let's dissect them: + +- We defined the logic of our Realm into a package called `counter`. +- The package-level `count` variable stores the active count for the Realm (it is stateful). +- `Increment` and `Decrement` are public Realm (Smart Contract) methods, and as such are callable by users. +- `Increment` and `Decrement` directly modify the `count` value by making it go up or down (change state). +- Calling the `Render` method would return the `count` value as a formatted string. Learn more about the `Render` + method and how it's used [here](../explanation/realms.md). + +:::info A note on constructors +Gno Realms support a concept taken from other programming languages - _constructors_. + +For example, to initialize the `count` variable with custom logic, we can specify that +logic within an `init` method, that is run **only once** on Realm deployment: + +[embedmd]:# (../assets/how-to-guides/simple-contract/init.gno go) +```go +package counter + +var count int + +// ... + +func init() { + count = 2 * 10 // arbitrary value +} + +// ... +``` + +::: + +## Conclusion + +That's it 🎉 + +You have successfully built a simple _Counter_ Realm that is ready to be deployed on the Gno chain and called by users. +In the upcoming guides, we will see how we can develop more complex Realm logic and have them interact +with outside tools like a wallet application. diff --git a/docs/how-to-guides/simple-library.md b/docs/how-to-guides/simple-library.md new file mode 100644 index 00000000000..3ed4ad11754 --- /dev/null +++ b/docs/how-to-guides/simple-library.md @@ -0,0 +1,133 @@ +--- +id: simple-library +--- + +# How to write a simple Gno Library (Package) + +## Overview + +This guide shows you how to write a simple library (Package) in Gnolang, which can be used by other Packages and Realms. +Packages are _stateless_, meaning they do not hold state like regular Realms (Smart Contracts). To learn more about the +intricacies of Packages, please see the [Packages reference](../explanation/packages.md). + +The Package we will be writing today will be a simple library for suggesting a random tapas dish. +We will define a set list of tapas, and define a method that randomly selects a dish from the list. + +## Prerequisites + +- **Text editor** + +:::info Editor support +The Gno language is based on Golang, but it does not have all the bells and whistles in major text editors like Go. +Advanced language features like IntelliSense are still in the works. + +Currently, we officially have language support +for [ViM](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md#vim-support), +[Emacs](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md#emacs-support) +and [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=harry-hov.gno). +::: + +## 1. Setting up the work directory + +We discussed Gno folder structures more in detail in +the [simple Smart Contract guide](simple-contract.md#1-setting-up-the-work-directory). +For now, we will just follow some rules outlined there. + +Create the main working directory for our Package: + +```bash +mkdir tapas-lib +``` + +Since we are building a simple tapas Package, inside our created `tapas-lib` directory, we can create another +directory named `p`, which stands for `package`: + +```bash +cd tapas-lib +mkdir p +``` + +Additionally, we will create another subdirectory that will house our Package code, named `tapas`: + +```bash +cd p +mkdir tapas +``` + +After setting up our work directory structure, we should have something like this: + +```text +tapas-lib/ +├─ p/ +│ ├─ tapas/ +│ │ ├─ // source code here +``` + +## 2. Create `tapas.gno` + +Now that the work directory structure is set up, we can go into the `tapas` sub-folder, and actually create +our tapas suggestion library logic: + +```bash +cd tapas +touch tapas.gno +``` + +Inside `tapas.gno`, we will define our library logic: + +[embedmd]:# (../assets/how-to-guides/simple-library/tapas.gno go) +```go +package tapas + +import ( + "gno.land/p/demo/rand" +) + +// List of tapas suggestions +var listOfTapas = []string{ + "Patatas Bravas", + "Gambas al Ajillo", + "Croquetas", + "Tortilla Española", + "Pimientos de Padrón", + "Jamon Serrano", + "Boquerones en Vinagre", + "Calamares a la Romana", + "Pulpo a la Gallega", + "Tostada con Tomate", + "Mejillones en Escabeche", + "Chorizo a la Sidra", + "Cazón en Adobo", + "Banderillas", + "Espárragos a la Parrilla", + "Huevos Rellenos", + "Tuna Empanada", + "Sardinas a la Plancha", +} + +// GetTapaSuggestion randomly selects and returns a tapa suggestion +func GetTapaSuggestion() string { + // Create a new instance of the random number generator. + // Notice that this is from an imported Gno library + generator := rand.New() + + // Generate a random index + randomIndex := generator.Intn(len(listOfTapas)) + + // Return the random suggestion + return listOfTapas[randomIndex] +} +``` + +There are a few things happening here, so let's dissect them: + +- We defined the logic of our library into a package called `tapas`. +- The package imports another gno package, which is deployed at `gno.land/p/demo/rand` +- We use the imported package inside of `GetTapaSuggestion` to generate a random index value for a tapa + +## Conclusion + +That's it 🎉 + +You have successfully built a simple tapas suggestion Package that is ready to be deployed on the Gno chain and imported +by other Packages and Realms. diff --git a/docs/how-to-guides/testing-gno.md b/docs/how-to-guides/testing-gno.md new file mode 100644 index 00000000000..8bb6f1bf451 --- /dev/null +++ b/docs/how-to-guides/testing-gno.md @@ -0,0 +1,185 @@ +--- +id: testing-gno +--- + +# How to test Gno Code + +## Overview + +In this guide, we will explore the available tooling in testing out the Gno Realms and Packages we write. +We will go over different CLI tools available to developers, gno testing libraries as well as +testing techniques that involve data mocking. + +## Prerequisites + +- **`gno` set up. Reference the [Installation](../getting-started/local-setup.md#3-installing-other-gno-tools) guide + for steps** + +## Example Realm + +For the purpose of this guide, we will be testing the simple *Counter* Realm created in +the [How to write a simple Gno Smart Contract (Realm)](simple-contract.md) guide. + +[embedmd]:# (../assets/how-to-guides/testing-gno/counter-1.gno go) +```go +// counter-app/r/counter/counter.gno + +package counter + +import "fmt" + +var count int + +func Increment() { + count++ +} + +func Decrement() { + count-- +} + +func Render(_ string) string { + return fmt.Sprintf("Count: %d", count) +} +``` + +## 1. Writing the Gno test + +Gno tests are written in the same manner and format as regular Go tests, just in `_test.gno` files. + +We can place the Gno tests for the `Counter` Realm in the same directory as `counter.gno`: + +```text +counter-app/ +├─ r/ +│ ├─ counter/ +│ │ ├─ counter.gno +│ │ ├─ counter_test.gno <--- the test source code +``` + +```bash +cd counter +touch counter_test.gno +``` + +What should be tested in this _Counter_ Realm example? +Mainly, we want to verify that: + +- Increment increments the value. +- Decrement decrements the value. +- Render returns a valid formatted value. + +Let's write the required unit tests: + +[embedmd]:# (../assets/how-to-guides/testing-gno/counter-2.gno go) +```go +// counter-app/r/counter/counter_test.gno + +package counter + +import "testing" + +func TestCounter_Increment(t *testing.T) { + // Reset the value + count = 0 + + // Verify the initial value is 0 + if count != 0 { + t.Fatalf("initial value != 0") + } + + // Increment the value + Increment() + + // Verify the initial value is 1 + if count != 1 { + t.Fatalf("initial value != 1") + } +} + +func TestCounter_Decrement(t *testing.T) { + // Reset the value + count = 0 + + // Verify the initial value is 0 + if count != 0 { + t.Fatalf("initial value != 0") + } + + // Decrement the value + Decrement() + + // Verify the initial value is 1 + if count != -1 { + t.Fatalf("initial value != -1") + } +} + +func TestCounter_Render(t *testing.T) { + // Reset the value + count = 0 + + // Verify the Render output + if Render("") != "Count: 0" { + t.Fatalf("invalid Render value") + } +} +``` + +:::warning Testing package-level variables + +In practice, it is not advisable to test and validate package level variables like this, as their value is mutated +between test runs. For the sake of keeping this guide simple, we went ahead and reset the variable value for each test, +however, +you should employ more robust test strategies. + +::: + +## 2. Running the Gno test + +To run the prepared Gno tests, we can utilize the `gno test` CLI tool. + +Simply point it to the location containing our testing source code, and the tests will execute. +For example, we can run the following command from the `counter-app/r/counter` directory: + +```bash +gno test -verbose -root-dir /Users/zmilos/Work/gno . +``` + +Let's look into the different parts of this command: + +- `-verbose` enables the verbose output. +- `-root-dir` specifies the root directory to our cloned `gno` GitHub repository +- `.` specifies the location containing our test files. Since we are already located in that directory, we specify + a `.`. + +Running the test command should produce a successful output: + +```bash +=== RUN TestCounter_Increment +--- PASS: TestCounter_Increment (0.00s) +=== RUN TestCounter_Decrement +--- PASS: TestCounter_Decrement (0.00s) +=== RUN TestCounter_Render +--- PASS: TestCounter_Render (0.00s) +ok ./. 1.00s +``` + +## Additional test support + +As we grow more familiar with Gno development, our Realm / Package logic can become more complex. As such, we need +more robust testing support in the form of mocking values ahead of time that would normally be only available on a +live (deployed) Realm / Package. + +Luckily, the Gno standard library provides ample support for functionality such as setting predefined values ahead of +time, such as the request caller address, or the calling package address. + +You can learn more about these methods, that are importable using the `std` import declaration, +in the [Standard Library](../reference/standard-library.md) reference section. + +## Conclusion + +That's it 🎉 + +You have successfully written and tested Gno code. Additionally, you have utilized the `gno test` tool, and understood +how it can be configured to make the developer experience smooth. diff --git a/docs/how-to-guides/write-simple-dapp.md b/docs/how-to-guides/write-simple-dapp.md new file mode 100644 index 00000000000..4c5c5fce032 --- /dev/null +++ b/docs/how-to-guides/write-simple-dapp.md @@ -0,0 +1,301 @@ +--- +id: write-simple-dapp +--- + +# How to write a simple dApp on Gno.land + +## Overview + +This guide will show you how to write a complete dApp that combines both a package and a realm. +Our app will allow any user to create a poll, and subsequently vote +YAY or NAY for any poll that has not exceeded the voting deadline. + +## Prerequisites + +- **Text editor** + +## Defining dApp functionality + +Our dApp will consist of a Poll package, which will handle all things related to the Poll struct, +and a Poll Factory realm, which will handle the user-facing functionality and rendering. + +For simplicity, we will define the functionality in plain text, and leave comments explaining the code. + +### Poll Package + +- Defines a `Poll` struct +- Defines a `NewPoll` constructor +- Defines `Poll` field getters +- Defines a `Vote` function +- Defines a `HasVoted` check method +- Defines a `VoteCount` getter method + +[embedmd]:# (../assets/how-to-guides/write-simple-dapp/poll-1.gno go) +```go +package poll + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// Main struct +type Poll struct { + title string + description string + deadline int64 // block height + voters *avl.Tree // addr -> yes / no (bool) +} + +// Getters +func (p Poll) Title() string { + return p.title +} + +func (p Poll) Description() string { + return p.description +} + +func (p Poll) Deadline() int64 { + return p.deadline +} + +func (p Poll) Voters() *avl.Tree { + return p.voters +} + +// Poll instance constructor +func NewPoll(title, description string, deadline int64) *Poll { + return &Poll{ + title: title, + description: description, + deadline: deadline, + voters: avl.NewTree(), + } +} + +// Vote Votes for a user +func (p *Poll) Vote(voter std.Address, vote bool) { + p.Voters().Set(string(voter), vote) +} + +// HasVoted vote: yes - true, no - false +func (p *Poll) HasVoted(address std.Address) (bool, bool) { + vote, exists := p.Voters().Get(string(address)) + if exists { + return true, vote.(bool) + } + return false, false +} + +// VoteCount Returns the number of yay & nay votes +func (p Poll) VoteCount() (int, int) { + var yay int + + p.Voters().Iterate("", "", func(key string, value interface{}) bool { + vote := value.(bool) + if vote == true { + yay = yay + 1 + } + }) + return yay, p.Voters().Size() - yay +} +``` + +A few remarks: + +- We are using the `std` library for accessing blockchain-related functionality and types, such as `std.Address`. +- Since the `map` data type is not deterministic in Go, we need to use the AVL tree structure, defined + under `p/demo/avl`. + It behaves similarly to a map; it maps a key of type `string` onto a value of any type - `interface{}`. +- We are importing the `p/demo/avl` package directly from on-chain storage, which can be accessed through the + path `gno.land/`. + As of October 2023, you can find already-deployed packages & libraries which provide additional Gno functionality in + the [monorepo](https://github.com/gnolang/gno), under the `examples/gno.land` folder. + +:::info +After testing the `Poll` package, we need to deploy it in order to use it in our realm. +Check out the [deployment](deploy.md) guide to learn how to do this. +::: + +### Poll Factory Realm + +Moving on, we can create the Poll Factory realm. + +The realm will contain the following functionality: + +- An exported `NewPoll` method, to allow users to create polls +- An exported `Vote` method, to allow users to pledge votes for any active poll +- A `Render` function to display the realm state + +[embedmd]:# (../assets/how-to-guides/write-simple-dapp/poll-2.gno go) +```go +package poll + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/poll" + "gno.land/p/demo/ufmt" +) + +// state variables +var ( + polls *avl.Tree // id -> Poll + pollIDCounter int +) + +func init() { + polls = avl.NewTree() + pollIDCounter = 0 +} + +// NewPoll - Creates a new Poll instance +func NewPoll(title, description string, deadline int64) string { + // get block height + if deadline <= std.GetHeight() { + return "Error: Deadline has to be in the future." + } + + // convert int ID to string used in AVL tree + id := ufmt.Sprintf("%d", pollIDCounter) + p := poll.NewPoll(title, description, deadline) + + // add new poll in avl tree + polls.Set(id, p) + + // increment ID counter + pollIDCounter = pollIDCounter + 1 + + return ufmt.Sprintf("Successfully created poll #%s!", id) +} + +// Vote - vote for a specific Poll +// yes - true, no - false +func Vote(pollID int, vote bool) string { + // get txSender + txSender := std.GetOrigCaller() + + id := ufmt.Sprintf("%d", pollID) + // get specific Poll from AVL tree + pollRaw, exists := polls.Get(id) + + if !exists { + return "Error: Poll with specified doesn't exist." + } + + // cast Poll into proper format + poll, _ := pollRaw.(*poll.Poll) + + voted, _ := poll.HasVoted(txSender) + if voted { + return "Error: You've already voted!" + } + + if poll.Deadline() <= std.GetHeight() { + return "Error: Voting for this poll is closed." + } + + // record vote + poll.Vote(txSender, vote) + + // update Poll in tree + polls.Set(id, poll) + + if vote == true { + return ufmt.Sprintf("Successfully voted YAY for poll #%s!", id) + } + return ufmt.Sprintf("Successfully voted NAY for poll #%s!", id) +} +``` + +With that we have written the core functionality of the realm, and all that is left is +the [Render function](http://localhost:3000/explanation/realms). +Its purpose is to help us display the state of the realm in Markdown, by formatting the state into a string buffer: + +[embedmd]:# (../assets/how-to-guides/write-simple-dapp/poll-3.gno go) +```go +func Render(path string) string { + var b bytes.Buffer + + b.WriteString("# Polls!\n\n") + + if polls.Size() == 0 { + b.WriteString("### No active polls currently!") + return b.String() + } + polls.Iterate("", "", func(key string, value interface{}) bool { + + // cast raw data from tree into Poll struct + p := value.(*poll.Poll) + ddl := p.Deadline() + + yay, nay := p.VoteCount() + yayPercent := 0 + nayPercent := 0 + + if yay+nay != 0 { + yayPercent = yay * 100 / (yay + nay) + nayPercent = nay * 100 / (yay + nay) + } + + b.WriteString( + ufmt.Sprintf( + "## Poll #%s: %s\n", + key, // poll ID + p.Title(), + ), + ) + + dropdown := "
\nPoll details
" + + b.WriteString(dropdown + "Description: " + p.Description()) + + b.WriteString( + ufmt.Sprintf("
Voting until block: %d
Current vote count: %d", + p.Deadline(), + p.Voters().Size()), + ) + + b.WriteString( + ufmt.Sprintf("
YAY votes: %d (%d%%)", yay, yayPercent), + ) + b.WriteString( + ufmt.Sprintf("
NAY votes: %d (%d%%)
", nay, nayPercent), + ) + + dropdown = "
\nVote details" + b.WriteString(dropdown) + + p.Voters().Iterate("", "", func(key string, value interface{}) bool { + + voter := key + vote := value.(bool) + + if vote == true { + b.WriteString( + ufmt.Sprintf("
%s voted YAY!", voter), + ) + } else { + b.WriteString( + ufmt.Sprintf("
%s voted NAY!", voter), + ) + } + return false + }) + + b.WriteString("
\n\n") + return false + }) + return b.String() +} +``` + +## Conclusion + +That's it 🎉 + +You have successfully built a simple but fully-fledged dApp using Gno! +Now you're ready to conquer new, more complex dApps in Gno. diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 00000000000..50e07442ea0 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,45 @@ +--- +id: overview +slug: / +--- + +# Overview + +## What is Gno.land? + +Gno.land is a Layer 1 blockchain platform that enables the execution of Smart Contracts using an interpreted +version of the Go programming language called Gnolang (Gno for short). + +### Key Features and Technology + +1. **Interpreted Gnolang**: Gno.land utilizes the Gnolang programming language, which is based on Go. It is executed + through a specialized virtual machine called the GnoVM, purpose-built for blockchain development with built-in + determinism and a modified standard library. While Gnolang + shares similarities with Go in terms of syntax, it currently lacks go routine support. However, this feature is + planned for future development, ensuring deterministic GnoVM executions. +2. **Consensus Protocol - Tendermint2**: Gno.land achieves consensus between blockchain nodes using the Tendermint2 + consensus protocol. This approach ensures secure and reliable network operation. +3. **Inter-Blockchain Communication (IBC)**: In the future, Gno.land will be able to communicate and exchange data with + other blockchain networks within the Cosmos ecosystem through the Inter-Blockchain Communication (IBC) protocol. + +### Why Go-based? + +The decision to base Gno.land's language on Go was influenced by the following factors: + +1. **Standard and Secure Language**: Go is a well-established and secure programming language, widely adopted in the + software development community. By leveraging Go's features, Gno.land benefits from a robust and proven foundation. +2. **User-Friendly**: Go's simplicity and ease of understanding make it beginner-friendly. This accessibility lowers the + entry barrier for developers to create Smart Contracts on the Gno.land platform. + +### How does it compare with Ethereum? + +In comparison to Ethereum, Gno.land offers distinct advantages: + +1. **Transparent and Auditable Smart Contracts**: Gno.land Smart Contracts are fully transparent and auditable by users + because the actual source code is uploaded to the blockchain. In contrast, Ethereum requires contracts to be + precompiled into bytecode, leading to less transparency as bytecode is stored on the blockchain, not the + human-readable source code. + +2. **General-Purpose Language**: Gno.land's Gnolang is a general-purpose language, similar to Go, extending its + usability beyond the context of blockchain. In contrast, Solidity is designed specifically for Smart Contracts on the + Ethereum platform. diff --git a/docs/peace.md b/docs/peace.md index e76faae1ca3..5eb876ecac8 100644 --- a/docs/peace.md +++ b/docs/peace.md @@ -1,3 +1,7 @@ +--- +id: peace +--- + # Peace! _or, Everyone is Invited to Gno.land, if you want!_ diff --git a/docs/reference/gno-js-client/getting-started.md b/docs/reference/gno-js-client/getting-started.md new file mode 100644 index 00000000000..188f084bd56 --- /dev/null +++ b/docs/reference/gno-js-client/getting-started.md @@ -0,0 +1,25 @@ +--- +id: gno-js-getting-started +--- + +# Getting Started + +[@gnolang/gno-js-client](https://github.com/gnolang/gno-js-client) is a JavaScript/TypeScript client implementation for Gno chains. It is an extension of the +[tm2-js-client](https://github.com/gnolang/tm2-js-client), but with Gno-specific functionality. + +## Key Features + +- Provides the ability to interact with Gno Realms / Packages +- Easy interaction with VM-specific ABCI queries + +## Installation + +To install `@gnolang/gno-js-client`, use your preferred package manager: + +```bash +yarn add @gnolang/gno-js-client +``` + +```bash +npm install @gnolang/gno-js-client +``` diff --git a/docs/reference/gno-js-client/gno-provider.md b/docs/reference/gno-js-client/gno-provider.md new file mode 100644 index 00000000000..a5248349d35 --- /dev/null +++ b/docs/reference/gno-js-client/gno-provider.md @@ -0,0 +1,124 @@ +--- +id: gno-js-provider +--- + +# Gno Provider + +The `Gno Provider` is an extension on the `tm2-js-client` `Provider`, +outlined [here](../tm2-js-client/Provider/provider.md). Both JSON-RPC and WS providers are included with the package. + +## Realm Methods + +### getRenderOutput + +Executes the Render(path) method in read-only mode + +#### Parameters + +* `packagePath` **string** the gno package path +* `path` **string** the render path +* `height` **number** the height for querying. + If omitted, the latest height is used (optional, default `0`) + +Returns **Promise** + +#### Usage + +```ts +await provider.getRenderOutput('gno.land/r/demo/demo_realm', ''); +// ## Hello World! +``` + +### getFunctionSignatures + +Fetches public facing function signatures + +#### Parameters + +* `packagePath` **string** the gno package path +* `height` **number** the height for querying. + If omitted, the latest height is used (optional, default `0`) + +Returns **Promise** + +#### Usage + +```ts +await provider.getFunctionSignatures('gno.land/r/demo/foo20'); +/* +[ + { FuncName: 'TotalSupply', Params: null, Results: [ [Object] ] }, + { + FuncName: 'BalanceOf', + Params: [ [Object] ], + Results: [ [Object] ] + }, + { + FuncName: 'Allowance', + Params: [ [Object], [Object] ], + Results: [ [Object] ] + }, + { + FuncName: 'Transfer', + Params: [ [Object], [Object] ], + Results: null + }, + { + FuncName: 'Approve', + Params: [ [Object], [Object] ], + Results: null + }, + { + FuncName: 'TransferFrom', + Params: [ [Object], [Object], [Object] ], + Results: null + }, + { FuncName: 'Faucet', Params: null, Results: null }, + { FuncName: 'Mint', Params: [ [Object], [Object] ], Results: null }, + { FuncName: 'Burn', Params: [ [Object], [Object] ], Results: null }, + { FuncName: 'Render', Params: [ [Object] ], Results: [ [Object] ] } +] + */ +``` + +### evaluateExpression + +Evaluates any expression in readonly mode and returns the results + +#### Parameters + +* `packagePath` **string** the gno package path +* `expression` **string** the expression to be evaluated +* `height` **number** the height for querying. + If omitted, the latest height is used (optional, default `0`) + +Returns **Promise** + +#### Usage + +```ts +await provider.evaluateExpression('gno.land/r/demo/foo20', 'TotalSupply()') +// (10100000000 uint64) +``` + +### getFileContent + +Fetches the file content, or the list of files if the path is a directory + +#### Parameters + +* `packagePath` **string** the gno package path +* `height` **number** the height for querying. + If omitted, the latest height is used (optional, default `0`) + +Returns **Promise** + +#### Usage + +```ts +await provider.getFileContent('gno.land/r/demo/foo20', 'TotalSupply()') +/* +foo20.gno +foo20_test.gno + */ +``` diff --git a/docs/reference/gno-js-client/gno-wallet.md b/docs/reference/gno-js-client/gno-wallet.md new file mode 100644 index 00000000000..7f7c44cd9b0 --- /dev/null +++ b/docs/reference/gno-js-client/gno-wallet.md @@ -0,0 +1,78 @@ +--- +id: gno-js-wallet +--- + +# Gno Wallet + +The `Gno Wallet` is an extension on the `tm2-js-client` `Wallet`, outlined [here](../tm2-js-client/wallet.md). + +## Account Methods + +### transferFunds + +Initiates a native currency transfer transaction between accounts + +#### Parameters + +* `to` **string** the bech32 address of the receiver +* `funds` **Map** the denomination -> value map for funds +* `fee` **TxFee** the custom transaction fee, if any + +Returns **Promise** + +#### Usage + +```ts +let fundsMap = new Map([ + ["ugnot", 10], +]); + +await wallet.transferFunds('g1flk9z2qmkgqeyrl654r3639rzgz7xczdfwwqw7', fundsMap); +// returns the transaction hash +``` + +### callMethod + +Invokes the specified method on a GNO contract + +#### Parameters + +* `path` **string** the gno package / realm path +* `method` **string** the method name +* `args` **string[]** the method arguments, if any +* `funds` **Map** the denomination -> value map for funds +* `fee` **TxFee** the custom transaction fee, if any + +Returns **Promise** + +#### Usage + +```ts +let fundsMap = new Map([ + ["ugnot", 10], +]); + +await wallet.callMethod('gno.land/r/demo/foo20', 'TotalBalance', []); +// returns the transaction hash +``` + +### deployPackage + +Deploys the specified package / realm + +#### Parameters + +* `gnoPackage` **MemPackage** the package / realm to be deployed +* `funds` **Map** the denomination -> value map for funds +* `fee` **TxFee** the custom transaction fee, if any + +Returns **Promise** + +#### Usage + +```ts +const memPackage: MemPackage = // ... + + await wallet.deployPackage(memPackage); +// returns the transaction hash +``` diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md new file mode 100644 index 00000000000..101c83a05c9 --- /dev/null +++ b/docs/reference/go-gno-compatibility.md @@ -0,0 +1,314 @@ +--- +id: go-gno-compatibility +--- + +# Go - Gno compatibility + +## Native keywords + +| keyword | support | +|-------------|------------------------| +| break | full | +| case | full | +| const | full | +| continue | full | +| default | full | +| defer | full | +| else | full | +| fallthrough | full | +| for | full | +| func | full | +| go | missing (after launch) | +| goto | full | +| if | full | +| import | full | +| interface | full | +| package | full | +| range | full | +| return | full | +| select | missing (after launch) | +| struct | full | +| switch | full | +| type | full | +| var | full | + +Generics are currently not implemented. + +## Native types + +| type | usage | persistency | +|-----------------------------------------------|------------------------|------------------------------------------------------------| +| `bool` | full | full | +| `byte` | full | full | +| `int`, `int8`, `int16`, `int32`, `int64` | full | full | +| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | full | full | +| `float32`, `float64` | full | full | +| `complex64`, `complex128` | missing (TBD) | missing | +| `uintptr`, `unsafe.Pointer` | missing | missing | +| `string` | full | full | +| `rune` | full | full | +| `interface{}` | full | full | +| `[]T` (slices) | full | full\* | +| `[N]T` (arrays) | full | full\* | +| `map[T1]T2` | full | full\* | +| `func (T1...) T2...` | full | full (needs more tests) | +| `*T` (pointers) | full | full\* | +| `chan T` (channels) | missing (after launch) | missing (after launch) | + +**\*:** depends on `T`/`T1`/`T2` + +Additional native types: + +| type | comment | +|----------|--------------------------------------------------------------------------------------------| +| `bigint` | Based on `math/big.Int` | +| `bigdec` | Based on https://github.com/cockroachdb/apd, (see https://github.com/gnolang/gno/pull/306) | + + +## Stdlibs + +Legend: + +* `nondet`: the standard library in question would require non-deterministic + behaviour to implement as in Go, such as cryptographical randomness or + os/network access. The library may still be implemented at one point, with a + different API. +* `gospec`: the standard library is very Go-specific -- for instance, it is used + for debugging information or for parsing/build Go source code. A Gno version + may exist at one point, likely with a different package name or semantics. +* `gnics`: the standard library requires generics. +* `test`: the standard library is currently available for use exclusively in + test contexts, and may have limited functionality. +* `cmd`: the Go standard library is a command -- a direct equivalent in Gno + would not be useful. +* `tbd`: whether to include the standard library or not is still up for + discussion. +* `todo`: the standard libary is to be added, and + [contributions are welcome.](https://github.com/gnolang/gno/issues/1267) +* `part`: the standard library is partially implemented in Gno, and contributions are + welcome to add the missing functionality. +* `full`: the standard library is fully implemented in Gno. + + + +| package | status | +|---------------------------------------------|----------| +| archive/tar | `tbd` | +| archive/zip | `tbd` | +| arena | `improb` | +| bufio | `full` | +| builtin | `full`[^1] | +| bytes | `full` | +| cmd/\* | `cmd` | +| compress/bzip2 | `tbd` | +| compress/flate | `tbd` | +| compress/gzip | `tbd` | +| compress/lzw | `tbd` | +| compress/zlib | `tbd` | +| container/heap | `tbd` | +| container/list | `tbd` | +| container/ring | `tbd` | +| context | `tbd` | +| crypto | `todo` | +| crypto/aes | `todo` | +| crypto/boring | `tbd` | +| crypto/cipher | `part` | +| crypto/des | `tbd` | +| crypto/dsa | `tbd` | +| crypto/ecdh | `tbd` | +| crypto/ecdsa | `tbd` | +| crypto/ed25519 | `tbd` | +| crypto/elliptic | `tbd` | +| crypto/hmac | `todo` | +| crypto/md5 | `test`[^2] | +| crypto/rand | `nondet` | +| crypto/rc4 | `tbd` | +| crypto/rsa | `tbd` | +| crypto/sha1 | `test`[^2] | +| crypto/sha256 | `part`[^3] | +| crypto/sha512 | `tbd` | +| crypto/subtle | `tbd` | +| crypto/tls | `nondet` | +| crypto/tls/fipsonly | `nondet` | +| crypto/x509 | `tbd` | +| crypto/x509/pkix | `tbd` | +| database/sql | `nondet` | +| database/sql/driver | `nondet` | +| debug/buildinfo | `gospec` | +| debug/dwarf | `gospec` | +| debug/elf | `gospec` | +| debug/gosym | `gospec` | +| debug/macho | `gospec` | +| debug/pe | `gospec` | +| debug/plan9obj | `gospec` | +| embed | `tbd` | +| encoding | `full` | +| encoding/ascii85 | `todo` | +| encoding/asn1 | `todo` | +| encoding/base32 | `todo` | +| encoding/base64 | `full` | +| encoding/binary | `part` | +| encoding/csv | `todo` | +| encoding/gob | `tbd` | +| encoding/hex | `full` | +| encoding/json | `todo` | +| encoding/pem | `todo` | +| encoding/xml | `todo` | +| errors | `part` | +| expvar | `tbd` | +| flag | `nondet` | +| fmt | `test`[^4] | +| go/ast | `gospec` | +| go/build | `gospec` | +| go/build/constraint | `gospec` | +| go/constant | `gospec` | +| go/doc | `gospec` | +| go/doc/comment | `gospec` | +| go/format | `gospec` | +| go/importer | `gospec` | +| go/parser | `gospec` | +| go/printer | `gospec` | +| go/scanner | `gospec` | +| go/token | `gospec` | +| go/types | `gospec` | +| hash | `full` | +| hash/adler32 | `full` | +| hash/crc32 | `todo` | +| hash/crc64 | `todo` | +| hash/fnv | `todo` | +| hash/maphash | `todo` | +| html | `todo` | +| html/template | `todo` | +| image | `tbd` | +| image/color | `tbd` | +| image/color/palette | `tbd` | +| image/draw | `tbd` | +| image/gif | `tbd` | +| image/jpeg | `tbd` | +| image/png | `tbd` | +| index/suffixarray | `tbd` | +| io | `full` | +| io/fs | `tbd` | +| io/ioutil | removed[^5] | +| log | `tbd` | +| log/slog | `tbd` | +| log/syslog | `nondet` | +| maps | `gnics` | +| math | `part` | +| math/big | `tbd` | +| math/bits | `todo` | +| math/cmplx | `tbd` | +| math/rand | `todo` | +| mime | `tbd` | +| mime/multipart | `tbd` | +| mime/quotedprintable | `tbd` | +| net | `nondet` | +| net/http | `nondet` | +| net/http/cgi | `nondet` | +| net/http/cookiejar | `nondet` | +| net/http/fcgi | `nondet` | +| net/http/httptest | `nondet` | +| net/http/httptrace | `nondet` | +| net/http/httputil | `nondet` | +| net/http/internal | `nondet` | +| net/http/pprof | `nondet` | +| net/mail | `nondet` | +| net/netip | `nondet` | +| net/rpc | `nondet` | +| net/rpc/jsonrpc | `nondet` | +| net/smtp | `nondet` | +| net/textproto | `nondet` | +| net/url | `full` | +| os | `nondet` | +| os/exec | `nondet` | +| os/signal | `nondet` | +| os/user | `nondet` | +| path | `full` | +| path/filepath | `nondet` | +| plugin | `nondet` | +| reflect | `todo` | +| regexp | `full` | +| regexp/syntax | `full` | +| runtime | `gospec` | +| runtime/asan | `gospec` | +| runtime/cgo | `gospec` | +| runtime/coverage | `gospec` | +| runtime/debug | `gospec` | +| runtime/metrics | `gospec` | +| runtime/msan | `gospec` | +| runtime/pprof | `gospec` | +| runtime/race | `gospec` | +| runtime/trace | `gospec` | +| slices | `gnics` | +| sort | `part`[^6] | +| strconv | `part` | +| strings | `full` | +| sync | `tbd` | +| sync/atomic | `tbd` | +| syscall | `nondet` | +| syscall/js | `nondet` | +| testing | `part` | +| testing/fstest | `tbd` | +| testing/iotest | `tbd` | +| testing/quick | `tbd` | +| text/scanner | `todo` | +| text/tabwriter | `todo` | +| text/template | `todo` | +| text/template/parse | `todo` | +| time | `full`[^7] | +| time/tzdata | `tbd` | +| unicode | `full` | +| unicode/utf16 | `tbd` | +| unicode/utf8 | `full` | +| unsafe | `nondet` | + +[^1]: `builtin` is a "fake" package that exists to document the behaviour of + some builtin functions. The "fake" package does not currently exist in Gno, + but [all functions up to Go 1.17 exist](https://pkg.go.dev/builtin@go1.17), + except for those relating to complex or channel types. +[^2]: `crypto/sha1` and `crypto/md5` implement "deprecated" hashing + algorithms, widely considered unsafe for cryptographic hashing. Decision on + whether to include these as part of the official standard libraries is still + pending. +[^3]: `crypto/sha256` is currently only implemented for `Sum256`, which should + still cover a majority of use cases. A full implementation is welcome. +[^4]: like many other encoding packages, `fmt` depends on `reflect` to be added. + For now, package `gno.land/p/demo/ufmt` may do what you need. In test + functions, `fmt` works. +[^5]: `io/ioutil` [is deprecated in Go.](https://pkg.go.dev/io/ioutil) + Its functionality has been moved to packages `os` and `io`. The functions + which have been moved in `io` are implemented in that package. +[^6]: `sort` has the notable omission of `sort.Slice`. You'll need to write a + bit of boilerplate, but you can use `sort.Interface` + `sort.Sort`! +[^7]: `time.Now` returns the block time rather than the system time, for + determinism. Concurrent functionality (such as `time.Ticker`) is not implemented. + +## Tooling (`gno` binary) + +| go command | gno command | comment | +|-------------------|------------------|-----------------------------------------------------------------------| +| go bug | | see https://github.com/gnolang/gno/issues/733 | +| go build | gno build | same intention, limited compatibility | +| go clean | gno clean | same intention, limited compatibility | +| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 | +| go env | | | +| go fix | | | +| go fmt | | gofmt (& similar tools, like gofumpt) works on gno code. | +| go generate | | | +| go get | | see `gno mod download`. | +| go help | gno $cmd --help | ie. `gno doc --help` | +| go install | | | +| go list | | | +| go mod | gno mod | | +| + go mod init | gno mod init | same behavior | +| + go mod download | gno mod download | same behavior | +| + go mod tidy | gno mod tidy | same behavior | +| | gno precompile | | +| go work | | | +| | gno repl | | +| go run | gno run | | +| go test | gno test | limited compatibility | +| go tool | | | +| go version | | | +| go vet | | | +| golint | gno lint | same intention | diff --git a/docs/reference/rpc-endpoints.md b/docs/reference/rpc-endpoints.md new file mode 100644 index 00000000000..790e4c49142 --- /dev/null +++ b/docs/reference/rpc-endpoints.md @@ -0,0 +1,502 @@ +--- +id: rpc-endpoints +--- + +# Gno RPC Endpoints + +## Common Parameters + +#### Response + +| Name | Type | Description | +| --------------- | ------ | --------------------------------- | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | Object | (upon success) The result object. | +| `error` | Object | (upon failure) The error object. | +| `error.code` | Number | The error code. | +| `error.message` | String | The error message. | +| `error.data` | String | The error data. | + +## Health Check + +Call with the `/health` path when verifying that the node is running. + +#### Response + +| Name | Type | Description | +| --------- | ------ | ---------------- | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | Object | {} | + +## Check Node Server Status + +Call with the `/status` path to check the information from a node. + +#### Response + +| Name | Type | Description | +| --------- | ---------------- | ------------------------------------- | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Status Result] | The result of the node server status. | + +#### Status Result + +| Name | Type | Description | +| ---------------- | ------ | ----------------------------------- | +| `node_info` | Object | General information about the node. | +| `sync_info` | Object | The sync information. | +| `validator_info` | Object | The validator information. | + +## Get Network Information + +Call with the `/net_info` path to check the network information from the node. + +#### Response + +| Name | Type | Description | +| --------- | ----------------- | ------------------------ | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[NetInfo Result] | The network information. | + +#### NetInfo Result + +| Name | Type | Description | +| ----------- | ---------- | ------------------ | +| `listening` | Boolean | Enables listening. | +| `listeners` | String \[] | List of listeners. | +| `n_peers` | String | Number of peers. | +| `peers` | String \[] | List of peers. | + +## Get Genesis Block Information + +Call with the `/genesis` path to retrieve information about the Genesis block from the node. + +#### Response + +| name | Type | Description | +| --------- | ------ | ------------------------------ | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | Object | The Genesis block information. | + +## Get Consensus Parameters + +Call with the /consensus\_params path to check the consensus algorithm parameters at the specified height. + +#### Parameters + +| Name | Description | +| -------- | ----------------- | +| `height` | The block height. | + +#### Response + +| Name | Type | Description | +| --------- | -------------------------- | ------------------------------------ | +| `jsonrpc` | String | The RPC Version. | +| `id` | String | The response ID. | +| `result` | \[Consensus Params Result] | The consensus parameter information. | + +#### Consensus Params Result + +| Name | Type | Description | +| ----------------------------- | ------ | -------------------------- | +| `block_height` | String | The block height. | +| `consensus_params` | Object | The parameter information. | +| `consensus_params.Block` | Object | The block parameters. | +| `consensus_params.Validattor` | Object | The validator parameters. | + +## Get Consensus State + +Call with the `/consensus_state` to get the consensus state of the Gnoland blockchain + +#### Response + +| Name | Type | Description | +| ------- | --------------------------- | -------------------------------- | +| jsonrpc | String | The RPC version. | +| id | String | The response ID. | +| result | \[Consensus State Response] | The consensus state information. | + +#### Consensus State Response + +| Name | Type | Description | +| --------------------------------- | ------ | -------------------------------- | +| `round_state` | Object | The consensus state object. | +| `round_state.height/round/step` | String | The block height / round / step. | +| `round_state.start_time` | String | The round start time. | +| `round_state.proposal_block_hash` | String | The proposal block hash. | +| `round_state.locked_block_hash` | String | The locked block hash. | +| `round_state.valid_block_hash` | String | The valid block hash. | +| `round_state.height_vote_set` | Object | - | + +## Get Commit + +Call with the `/commit` path to retrieve commit information at the specified block height. + +#### Parameters + +| Name | Description | +| -------- | ----------------- | +| `height` | The block height. | + +#### Response + +| Name | Type | Description | +| --------- | ---------------- | ----------------------- | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Commit Result] | The commit information. | + +#### Commit Result + +| Name | Type | Description | +| -------------- | ------- | ------------------------- | +| signed\_header | Object | The signed header object. | +| canonical | Boolean | Returns commit state. | + +## Get Block Information + +Call with the `/block` path to retrieve block information at the specified height. + +#### Parameters + +| Name | Description | +| -------- | ----------------- | +| `height` | The block height. | + +#### Response + +| Name | Type | Description | +| --------- | --------------- | ----------------------- | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Block Result] | The commit information. | + +#### Block Result + +| Name | Type | Description | +| ------------ | ------ | ---------------------- | +| `block_meta` | Object | The block metadata. | +| `block` | Object | The block information. | + +## Get Block Results + +Call with the `/block_results` path to retrieve block processing information at the specified height. + +#### Parameters + +| Name | Description | +| -------- | ----------------- | +| `height` | The block height. | + +#### Response + +| Name | Type | Description | +| --------- | --------------- | ------------------ | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Block Result] | The result object. | + +#### Block Result + +| Name | Type | Description | +| --------- | ------------------------ | ------------------------------------- | +| `height` | Object | The block height. | +| `results` | \[Block Result Info] \[] | The list of block processing results. | + +#### Block Result Info + +| Name | Type | Description | +| --------------------------- | ---------- | -------------------------------- | +| `deliver_tx` | Object \[] | The list of transaction results. | +| `deliver_tx[].ResponseBase` | Object | The transaction response object. | +| `deliver_tx[].GasWanted` | String | Maximum amount of gas to use. | +| `deliver_tx[].GasUsed` | String | Actual gas used. | +| `begin_block` | Object | Previous block information. | +| `end_block` | Object | Next block information. | + +## Get Block List + +Call with the `/blockchain` path to retrieve information about blocks within a specified range. + +#### Parameters + +| Name | Description | +| ----------- | ------------------------- | +| `minHeight` | The minimum block height. | +| `maxHeight` | The maximum block height. | + +#### Response + +| Name | Type | Description | +| --------- | -------------------- | ------------------ | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Blockchain Result] | The result object. | + +#### Blockchain Result + +| Name | Type | Description | +| ------------- | ---------- | --------------------------- | +| `last_height` | String | The latest block height. | +| `block_meta` | Object \[] | The list of block metadata. | + +## Get a No. of Unconfirmed Transactions + +Call with the `/num_unconfirmed_txs` path to get data about unconfirmed transactions. + +#### Response + +| Name | Type | Description | +| --------- | ----------------------------- | ------------------ | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Num Unconfirmed Txs Result] | The result object. | + +#### Num Unconfirmed Txs Result + +| Name | Type | Description | +| ------------- | ------ | --------------------------- | +| `n_txs` | String | The number of transactions. | +| `total` | String | The total number. | +| `total_bytes` | String | Total bytes. | +| `txs` | null | - | + +## Get a List of Unconfirmed Transactions + +Call with the `/unconfirmed_txs` path to get a list of unconfirmed transactions. + +#### Parameters + +| Name | Description | +| ------- | --------------------------------------- | +| `limit` | The maximum transaction numbers to get. | + +#### Response + +| Name | Type | Description | +| --------- | ------------------------- | ------------------ | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Unconfirmed Txs Result] | The result object. | + +#### Unconfirmed Txs Result + +| Name | Type | Description | +| ------------- | ---------- | ----------------------------------- | +| `n_txs` | String | The number of transactions. | +| `total` | String | The total number. | +| `total_bytes` | String | Total bytes. | +| `txs` | Object \[] | A list of unconfirmed transactions. | + +## Get a List of Validators + +Call with the `/validators` path to get a list of validators at a specific height. + +#### Parameters + +| Name | Description | +| -------- | ----------------------------------------- | +| `height` | The block height (default: newest block). | + +#### Response + +| Name | Type | Description | +| --------- | -------------------- | ------------------ | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Validators Result] | The result object. | + +#### Validators Result + +| Name | Type | Description | +| -------------- | ---------------- | ----------------------- | +| `block_height` | Object | The block height. | +| `validators` | \[Validator] \[] | The list of validators. | + +#### Validator + +| Name | Type | Description | +| ------------------- | ---------- | ---------------------------------------- | +| `address` | String | The address of the validator. | +| `pub_key` | Object \[] | The public key object of the validator. | +| `pub_key.@type` | String | The type of validator's public key. | +| `pub_key.value` | String | The value of the validator's public key. | +| `voting_power` | String | Voting power of the validator. | +| `proposer_priority` | String | The priority of the proposer. | + +## Broadcast a Transaction - Asynchronous + +Call with the `/broadcast_tx_async` path to create and broadcast a transaction without waiting for the transaction response. + +#### Parameters + +| Name | Description | +| ---- | ------------------------------------------- | +| `tx` | The value of the signed transaction binary. | + +#### Response + +| Name | Type | Description | +| --------- | --------------------- | ------------------ | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Transaction Result] | The result object. | + +#### Transaction Result + +| Name | Type | Description | +| ----- | ------ | ---------------------------- | +| hash | String | The transaction hash. | +| data | Object | The transaction data object. | +| error | Object | The error object. | +| log | String | The log information. | + +## Broadcast a Transaction - Synchronous + +Call with the `/broadcast_tx_sync` path to create and broadcast a transaction, then wait for the transaction response. + +#### Parameters + +| Name | Description | +| ---- | ------------------------------------------- | +| `tx` | The value of the signed transaction binary. | + +#### Response + +| Name | Type | Description | +| --------- | --------------------- | ------------------ | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Transaction Result] | The result object. | + +#### Transaction Result + +| Name | Type | Description | +| ----- | ------ | ---------------------------- | +| hash | String | The transaction hash. | +| data | Object | The transaction data object. | +| error | Object | The error object. | +| log | String | The log information. | + +## (NOT RECOMMENDED) Broadcast Transaction and Get Commit Information + +Call with the `/broadcast_tx_commit` path to create and broadcast a transaction, then wait for the transaction response and the commit response. + +#### Parameters + +| Name | Description | +| ---- | ------------------------------------------- | +| `tx` | The value of the signed transaction binary. | + +#### Response + +| Name | Type | Description | +| --------- | ---------------------------- | ------------------ | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[Transaction Commit Result] | The result object. | + +#### Transaction Commit Result + +| Name | Type | Description | +| ------------ | ------ | ----------------------------------------------------------- | +| `height` | String | The height of the block when the transaction was committed. | +| hash | String | The transaction hash. | +| `deliver_tx` | Object | The delivered transaction information. | +| `check_tx` | Object | The committed transaction information. | + +## ABCI + +### Get ABCI Information + +Call with the `/abci_info` path to get the latest information about the ABCI. + +#### Response + +| Name | Type | Description | +| --------- | ------------------- | ----------------------- | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[ABCI Info Result] | The commit information. | + +#### ABCI Info Result + +| Name | Type | Description | +| --------------------------- | ---------------- | -------------------------- | +| `response` | Object | The metadata of the block. | +| `response.ResponseBase` | \[ABCI Response] | The ABCI response data. | +| `response.ABCIVersion` | String | The ABCI version. | +| `response.AppVersion` | String | The app version. | +| `response.LastBlockHeight` | String | The latest block height. | +| `response.LastBlockAppHash` | String | The latest block hash. | + +#### ABCI Response + +| Name | Type | Description | +| ------ | ---------- | --------------------------------- | +| Data | String | The Base64-encoded response data. | +| Error | Object | The ABCI response error object. | +| Events | Object \[] | The list of event objects. | +| Log | String | The ABCI response log. | +| Info | String | The ABCI response information. | + +### Get ABCI Query + +Call with the `/abci_query` to get information via the ABCI Query. + +#### Query + +| Name | Description | +| ------------------------- | ------------------------------------------------------------------ | +| `auth/accounts/{ADDRESS}` | Returns the account information. | +| `bank/balances/{ADDRESS}` | Returns the balance information about the account. | +| `vm/qfuncs` | Returns public facing function signatures as JSON. | +| `vm/qfile` | Returns the file bytes, or list of files if directory. | +| `vm/qrender` | Calls `.Render()` in readonly mode. | +| `vm/qeval` | Evaluates any expression in readonly mode and returns the results. | +| `vm/store` | (not yet supported) Fetches items from the store. | +| `vm/package` | (not yet supported) Fetches a package's files. | + +#### Parameters + +| Name | Description | +| ------------------- | ------------------------------------------------ | +| `path` | The query path. | +| `data` | The data from the query path. | +| (optional) `height` | The block height (default: latest block height). | +| (optional) `prove` | The validation status. | + +#### Response + +| Name | Type | Description | +| --------- | -------------------- | ----------------------- | +| `jsonrpc` | String | The RPC version. | +| `id` | String | The response ID. | +| `result` | \[ABCI Query Result] | The commit information. | + +#### ABCI Query Result + +| Name | Type | Description | +| ----------------------- | ---------------- | -------------------------- | +| `response` | Object | The metadata of the block. | +| `response.ResponseBase` | \[ABCI Response] | The ABCI response data. | +| `response.Key` | String | The key. | +| `response.Value` | String | The value. | +| `response.Proof` | String | The validation ID. | +| `response.Height` | String | The block height. | + +#### ABCI Response + +| Name | Type | Description | +| ------ | ---------- | --------------------------------- | +| Data | String | The Base64-encoded response data. | +| Error | Object | The ABCI response error object. | +| Events | Object \[] | The list of event objects. | +| Log | String | The ABCI response log. | +| Info | String | The ABCI response information. | diff --git a/docs/reference/standard-library.md b/docs/reference/standard-library.md new file mode 100644 index 00000000000..71fad4943e6 --- /dev/null +++ b/docs/reference/standard-library.md @@ -0,0 +1,68 @@ +--- +id: standard-library +--- + +# Gno Standard Library + +When developing a realm in Gnolang, developers may utilize libraries in [stdlibs](https://github.com/gnolang/gno/tree/master/stdlibs). These are the core standard packages provided for Gnolang [Realms ](../explanation/realms.md)& [Packages](../explanation/packages.md). + +Libraries can be imported in a manner similar to how libraries are imported in Golang. + +An example of importing a `std` library in Gnolang is demonstrated in the following command: + +```go +import "std" +``` + +Let's explore some of the most commonly used modules in the library. + +## `stdshim` + +### `banker.gno` + +A library for manipulating `Coins`. Interfaces that must be implemented when using this library are as follows: + +[embedmd]:# (../assets/reference/standard-library/std-1.gno go) +```go +// returns the list of coins owned by the address +GetCoins(addr Address) (dst Coins) + +// sends coins from one address to another +SendCoins(from, to Address, amt Coins) + +// returns the total supply of the coin +TotalCoin(denom string) int64 + +// issues coins to the address +IssueCoin(addr Address, denom string, amount int64) + +// burns coins from the address +RemoveCoin(addr Address, denom string, amount int64) +``` + +### `coins.gno` + +A library that declares structs for expressing `Coins`. The struct looks like the following: + +[embedmd]:# (../assets/reference/standard-library/std-2.gno go) +```go +type Coin struct { + Denom string `json:"denom"` // the symbol of the coin + Amount int64 `json:"amount"` // the quantity of the coin +} +``` + +### `testing` + +A library that declares `*testing`, which is a tool used for the creation and execution of test cases during the development and testing phase of realms utilizing the `gno` CLI tool with the `test` option. + +There are 3 types of testing in `gno`. + +* Type `T` + * Type passed to Test functions to manage test state and support formatted test logs. +* Type `B` + * Type passed to Benchmark functions. + * Manage benchmark timing. + * Specify the number of iterations to run. +* Type `PB` + * Used by `RunParallel` for running parallel benchmarks. diff --git a/docs/reference/tm2-js-client/Provider/json-rpc-provider.md b/docs/reference/tm2-js-client/Provider/json-rpc-provider.md new file mode 100644 index 00000000000..b7700e1d97c --- /dev/null +++ b/docs/reference/tm2-js-client/Provider/json-rpc-provider.md @@ -0,0 +1,22 @@ +--- +id: tm2-js-json-rpc-provider +--- + +# JSON-RPC Provider + +Provider based on JSON-RPC HTTP requests. + +### new JSONRPCProvider + +Creates a new instance of the JSON-RPC Provider + +#### Parameters + +* `baseURL` **string** the JSON-RPC URL of the node + +#### Usage + +```ts +new JSONRPCProvider('http://staging.gno.land:36657'); +// provider is created +``` diff --git a/docs/reference/tm2-js-client/Provider/provider.md b/docs/reference/tm2-js-client/Provider/provider.md new file mode 100644 index 00000000000..da6168eb6c2 --- /dev/null +++ b/docs/reference/tm2-js-client/Provider/provider.md @@ -0,0 +1,443 @@ +--- +id: tm2-js-provider +--- + +# Overview + +A `Provider` is an interface that abstracts the interaction with the Tendermint2 chain, making it easier for users to +communicate with it. Rather than requiring users to understand which endpoints are exposed, what their return types are, +and how they are parsed, the `Provider` abstraction handles all of this behind the scenes. It exposes useful API methods +that users can use and expects concrete types in return. + +Currently, the `tm2-js-client` package provides support for two Provider implementations: + +- [JSON-RPC Provider](json-rpc-provider.md): executes each call as a separate HTTP RPC call. +- [WS Provider](ws-provider.md): executes each call through an active WebSocket connection, which requires closing when + not needed anymore. + +## Account Methods + +### getBalance + +Fetches the denomination balance of the account + +#### Parameters + +* `address` **string** the bech32 address of the account +* `denomination` **string** the balance denomination (optional, default `ugnot`) +* `height` **number** the height for querying. + If omitted, the latest height is used (optional, default `0`) + +Returns **Promise** + +#### Usage + +```ts +await provider.getBalance('g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq', 'atom'); +// 100 +``` + +### getAccountSequence + +Fetches the account sequence + +#### Parameters + +* `address` **string** the bech32 address of the account +* `height` **number** the height for querying. + If omitted, the latest height is used. (optional, default `0`) + +Returns **Promise** + +#### Usage + +```ts +await provider.getAccountSequence('g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq'); +// 42 +``` + +### getAccountNumber + +Fetches the account number. Errors out if the account +is not initialized + +#### Parameters + +* `address` **string** the bech32 address of the account +* `height` **number** the height for querying. + If omitted, the latest height is used (optional, default `0`) + +Returns **Promise** + +#### Usage + +```ts +await provider.getAccountNumber('g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq'); +// 100 +``` + +## Block methods + +### getBlock + +Fetches the block at the specific height, if any + +#### Parameters + +* `height` **number** the height for querying + +Returns **Promise** + +#### Usage + +```ts +await provider.getBlock(1); +/* +{ + block_meta: { + block_id: { + hash: "TxHKEGxFm/4+D7gxOJdVUaR+xTDZzlPrCVXuVm7SqHw=", + parts: { + total: "1", + hash: "+dqI9oyngnnlKyno7y+RxCLEPA9FxWA/MmXyJ4uoJAY=" + } + }, + header: { + version: "v1.0.0-rc.0", + chain_id: "dev", + height: "1", + time: "2023-05-01T10:32:20.807541Z", + num_txs: "0", + total_txs: "0", + app_version: "", + last_block_id: { + hash: null, + parts: { + total: "0", + hash: null + } + }, + last_commit_hash: null, + data_hash: null, + validators_hash: "FnuBaDvDLg4FotGRcZAFvhLkEjkb+kNLaAZrAVhL5Aw=", + next_validators_hash: "FnuBaDvDLg4FotGRcZAFvhLkEjkb+kNLaAZrAVhL5Aw=", + consensus_hash: "uKhnXFmGUkxgQSJf17ogbYLNXDo3UEPwQvzddo4Vkuw=", + app_hash: null, + last_results_hash: null, + proposer_address: "g1vsqzyy9a4h9ah8cxzkaw09rpzy369mkl70lfdk" + } + }, + block: { + header: { + version: "v1.0.0-rc.0", + chain_id: "dev", + height: "1", + time: "2023-05-01T10:32:20.807541Z", + num_txs: "0", + total_txs: "0", + app_version: "", + last_block_id: { + hash: null, + parts: { + total: "0", + hash: null + } + }, + last_commit_hash: null, + data_hash: null, + validators_hash: "FnuBaDvDLg4FotGRcZAFvhLkEjkb+kNLaAZrAVhL5Aw=", + next_validators_hash: "FnuBaDvDLg4FotGRcZAFvhLkEjkb+kNLaAZrAVhL5Aw=", + consensus_hash: "uKhnXFmGUkxgQSJf17ogbYLNXDo3UEPwQvzddo4Vkuw=", + app_hash: null, + last_results_hash: null, + proposer_address: "g1vsqzyy9a4h9ah8cxzkaw09rpzy369mkl70lfdk" + }, + data: { + txs: null + }, + last_commit: { + block_id: { + hash: null, + parts: { + total: "0", + hash: null + } + }, + precommits: null + } + } +} +*/ +``` + +### getBlockResult + +Fetches the block at the specific height, if any + +#### Parameters + +* `height` **number** the height for querying + +Returns **Promise** + +#### Usage + +```ts +await provider.getBlockResult(1); +/* +{ + height: "1", + results: { + deliver_tx: null, + end_block: { + ResponseBase: { + Error: null, + Data: null, + Events: null, + Log: "", + Info: "" + }, + ValidatorUpdates: null, + ConsensusParams: null, + Events: null + }, + begin_block: { + ResponseBase: { + Error: null, + Data: null, + Events: null, + Log: "", + Info: "" + } + } + } +} +*/ +``` + +### getBlockNumber + +Fetches the latest block number from the chain + +Returns **Promise** + +#### Usage + +```ts +await provider.getBlockNumber(); +// 1300 +``` + +## Network methods + +### getNetwork + +Fetches the network information + +Returns **Promise** + +#### Usage + +```ts +await provider.getNetwork(); +/* +{ + listening: true, + listeners: [ + "Listener(@)" + ], + n_peers: "0", + peers: [] +} +*/ +``` + +### getConsensusParams + +Fetches the consensus params for the specific block height + +#### Parameters + +* `height` **number** the height for querying + +Returns **Promise** + +#### Usage + +```ts +await provider.getConsensusParams(1); +/* +{ + block_height: "1", + consensus_params: { + Block: { + MaxTxBytes: "1000000", + MaxDataBytes: "2000000", + MaxBlockBytes: "0", + MaxGas: "10000000", + TimeIotaMS: "100" + }, + Validator: { + PubKeyTypeURLs: [ + "/tm.PubKeyEd25519" + ] + } + } +} +*/ +``` + +### getStatus + +Fetches the current node status + +Returns **Promise** + +#### Usage + +```ts +await provider.getStatus(); +/* +{ + node_info: { + version_set: [ + { + Name: "abci", + Version: "v1.0.0-rc.0", + Optional: false + }, + { + Name: "app", + Version: "", + Optional: false + }, + { + Name: "bft", + Version: "v1.0.0-rc.0", + Optional: false + }, + { + Name: "blockchain", + Version: "v1.0.0-rc.0", + Optional: false + }, + { + Name: "p2p", + Version: "v1.0.0-rc.0", + Optional: false + } + ], + net_address: "g1z0wa6rspsshkm2k7jlqvnjs8jdt4kvg4e9j640@0.0.0.0:26656", + network: "dev", + software: "", + version: "v1.0.0-rc.0", + channels: "QCAhIiMw", + moniker: "voyager.lan", + other: { + tx_index: "off", + rpc_address: "tcp://127.0.0.1:26657" + } + }, + sync_info: { + latest_block_hash: "x5ewEBhf9+MGXbEFkUdOm3RsE40D+plUia2u0PuVfHs=", + latest_app_hash: "7dB/+EmqLqEX2RkH2Zx+GcFo8c2vTs2ttW8urYyyFT4=", + latest_block_height: "55", + latest_block_time: "2023-05-06T11:28:35.643575Z", + catching_up: false + }, + validator_info: { + address: "g1vsqzyy9a4h9ah8cxzkaw09rpzy369mkl70lfdk", + pub_key: { + "@type": "/tm.PubKeyEd25519", + value: "X8ZS1DYu1eJ3HYnZ0OWk+0GgCdI7zA++kgWiprWMs3w=" + }, + voting_power: "0" + } +} +*/ +``` + +### getGasPrice + +**NOTE: Not supported yet** + +Fetches the current (recommended) average gas price + +Returns **Promise** + +### estimateGas + +**NOTE: Not supported yet** + +Estimates the gas limit for the transaction + +#### Parameters + +* `tx` **Tx** the transaction that needs estimating + +Returns **Promise** + +## Transaction methods + +### sendTransaction + +Sends the transaction to the node for committing and returns the transaction hash. +The transaction needs to be signed beforehand. + +#### Parameters + +* `tx` **string** the base64-encoded signed transaction + +Returns **Promise** + +#### Usage + +```ts +await provider.sendTransaction('ZXhhbXBsZSBzaWduZWQgdHJhbnNhY3Rpb24'); +// "dHggaGFzaA==" +``` + +### waitForTransaction + +Waits for the transaction to be committed on the chain. +NOTE: This method will not take in the fromHeight parameter once +proper transaction indexing is added - the implementation should +simply try to fetch the transaction first to see if it's included in a block +before starting to wait for it; Until then, this method should be used +in the sequence: +get latest block -> send transaction -> waitForTransaction(block before send) + +#### Parameters + +* `hash` **string** The transaction hash +* `fromHeight` **number** The block height used to begin the search (optional, default `latest`) +* `timeout` **number** Optional wait timeout in MS (optional, default `15000`) + +Returns **Promise** + +#### Usage + +```ts +await provider.waitForTransaction('ZXhhbXBsZSBzaWduZWQgdHJhbnNhY3Rpb24'); +/* +{ + messages:[], // should be filled with the appropriate message type + fee:{ + gasWanted: "100", + gasFee: "1ugnot" + }, + signatures:[ + { + pubKey:[ + { + type: "/tm.PubKeySecp256k1" + value: "X8ZS1DYu1eJ3HYnZ0OWk+0GgCdI7zA++kgWiprWMs3w=" + } + ], + signature: "X8ZS1DYu1eJ3HYnZ0OWk+0GgCdI7zA++kgWiprWMs3w=" + } + ], + memo: "check out gno.land!" +} +*/ +``` diff --git a/docs/reference/tm2-js-client/Provider/utility.md b/docs/reference/tm2-js-client/Provider/utility.md new file mode 100644 index 00000000000..3f0181ccc39 --- /dev/null +++ b/docs/reference/tm2-js-client/Provider/utility.md @@ -0,0 +1,116 @@ +--- +id: tm2-js-utility +--- + +# Utility Helpers + +## Provider Helpers + +### extractBalanceFromResponse + +Extracts the specific balance denomination from the ABCI response + +#### Parameters + +* `abciData` **(string | null)** the base64-encoded ABCI data +* `denomination` **string** the required denomination + +### extractSequenceFromResponse + +Extracts the account sequence from the ABCI response + +#### Parameters + +* `abciData` **(string | null)** the base64-encoded ABCI data + +Returns **number** + +### extractAccountNumberFromResponse + +Extracts the account number from the ABCI response + +#### Parameters + +* `abciData` **(string | null)** the base64-encoded ABCI data + +Returns **number** + +### waitForTransaction + +Waits for the transaction to be committed to a block in the chain +of the specified provider. This helper does a search for incoming blocks +and checks if a transaction + +#### Parameters + +* `provider` **Provider** the provider instance +* `hash` **string** the base64-encoded hash of the transaction +* `fromHeight` **number** the starting height for the search. If omitted, it is the latest block in the chain ( + optional, default `latest`) +* `timeout` **number** the timeout in MS for the search (optional, default `15000`) + +Returns **Promise** + +## Request Helpers + +### newRequest + +Creates a new JSON-RPC 2.0 request + +#### Parameters + +* `method` **string** the requested method +* `params` **Array?** the requested params, if any + +Returns **RPCRequest** + +### newResponse + +Creates a new JSON-RPC 2.0 response + +#### Parameters + +* `result` **Result** the response result, if any +* `error` **RPCError** the response error, if any + +Returns **RPCResponse** + +### parseABCI + +Parses the base64 encoded ABCI JSON into a concrete type + +#### Parameters + +* `data` **string** the base64-encoded JSON + +Returns **Result** + +### stringToBase64 + +Converts a string into base64 representation + +#### Parameters + +* `str` **string** the raw string + +Returns **string** + +### base64ToUint8Array + +Converts a base64 string into a Uint8Array representation + +#### Parameters + +* `str` **string** the base64-encoded string + +Returns **Uint8Array** + +### uint8ArrayToBase64 + +Converts a Uint8Array into base64 representation + +#### Parameters + +* `data` **Uint8Array** the Uint8Array to be encoded + +Returns **string** diff --git a/docs/reference/tm2-js-client/Provider/ws-provider.md b/docs/reference/tm2-js-client/Provider/ws-provider.md new file mode 100644 index 00000000000..ef91f45d4e2 --- /dev/null +++ b/docs/reference/tm2-js-client/Provider/ws-provider.md @@ -0,0 +1,95 @@ +--- +id: tm2-js-ws-provider +--- + +# WebSocket Provider + +Provider based on WS JSON-RPC requests. + +### new WSProvider + +Creates a new instance of the WebSocket Provider + +#### Parameters + +* `baseURL` **string** the WS URL of the node +* `requestTimeout` **number** the timeout for the WS request (in MS) + +#### Usage + +```ts +new WSProvider('ws://staging.gno.land:36657/ws'); +// provider with WS connection is created +``` + +### closeConnection + +Closes the WS connection. Required when done working +with the WS provider + +#### Usage + +```ts +const wsProvider = new WSProvider('ws://staging.gno.land:36657/ws'); + +wsProvider.closeConnection(); +// WS connection is now closed +``` + +### sendRequest + +Sends a request to the WS connection, and resolves +upon receiving the response + +#### Parameters + +* `request` **RPCRequest** the RPC request + +Returns **Promise>** + +#### Usage + +```ts +const request: RPCRequest = // ... + +const wsProvider = new WSProvider('ws://staging.gno.land:36657/ws'); + +wsProvider.sendRequest(request); +// request is sent over the open WS connection +``` + +### parseResponse + +Parses the result from the response + +#### Parameters + +* `response` **RPCResponse** the response to be parsed + +Returns **Result** + +#### Usage + +```ts +const response: RPCResponse = // ... + +const wsProvider = new WSProvider('ws://staging.gno.land:36657/ws'); + +wsProvider.parseResponse(response); +// response is parsed +``` + +### waitForOpenConnection + +Waits for the WS connection to be established + +Returns **Promise** + +#### Usage + +```ts +const wsProvider = new WSProvider('ws://staging.gno.land:36657/ws'); + +await wsProvider.waitForOpenConnection() +// status of the connection is: CONNECTED +``` diff --git a/docs/reference/tm2-js-client/Signer/key.md b/docs/reference/tm2-js-client/Signer/key.md new file mode 100644 index 00000000000..3c40fa427d2 --- /dev/null +++ b/docs/reference/tm2-js-client/Signer/key.md @@ -0,0 +1,30 @@ +--- +id: tm2-js-key +--- + +# Key Signer + +Private key-based signer instance + +### new KeySigner + +Creates a new instance of the private-key KeySigner + +#### Parameters + +* `privateKey` **Uint8Array** the raw Secp256k1 private key +* `publicKey` **Uint8Array** the raw Secp256k1 public key +* `addressPrefix` **string** the address prefix + +#### Usage + +```ts +// Generate the public / private key from somewhere +const {publicKey, privateKey} = await generateKeyPair( + entropyToMnemonic(generateEntropy()), + index ? index : 0 +); + +new KeySigner(privateKey, publicKey); +// new Secp256k1 key signer created +``` diff --git a/docs/reference/tm2-js-client/Signer/ledger.md b/docs/reference/tm2-js-client/Signer/ledger.md new file mode 100644 index 00000000000..71a7682c3e3 --- /dev/null +++ b/docs/reference/tm2-js-client/Signer/ledger.md @@ -0,0 +1,27 @@ +--- +id: tm2-js-ledger +--- + +# Ledger Signer + +Ledger device-based signer instance + +### new LedgerSigner + +Creates a new instance of the Ledger device signer, using the provided `LedgerConnector` + +#### Parameters + +* `connector` **LedgerConnector** the Ledger connector +* `accountIndex` **number** the desired account index +* `addressPrefix` **string** the address prefix + +#### Usage + +```ts +const accountIndex: number = 10 // for ex. 10th account in the derivation +const connector: LedgerConnector = // ... + +new LedgerSigner(connector, accountIndex); +// new Ledger device signer created +``` diff --git a/docs/reference/tm2-js-client/Signer/signer.md b/docs/reference/tm2-js-client/Signer/signer.md new file mode 100644 index 00000000000..fd3945cc2ae --- /dev/null +++ b/docs/reference/tm2-js-client/Signer/signer.md @@ -0,0 +1,96 @@ +--- +id: tm2-js-signer +--- + +# Overview + +A `Signer` is an interface that abstracts the interaction with a single Secp256k1 key pair. It exposes methods for +signing data, verifying signatures, and getting metadata associated with the key pair, such as the address. + +Currently, the `tm2-js-client` package provides support for two `Signer` implementations: + +- [Key](key.md): a signer that is based on a raw Secp256k1 key pair. +- [Ledger](ledger.md): a signer that is based on a Ledger device, with all interaction flowing through the user's + device. + +## API + +### getAddress + +Returns the address associated with the signer's public key + +Returns **Promise** + +#### Usage + +```ts +await signer.getAddress(); +// "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" +``` + +### getPublicKey + +Returns the signer's Secp256k1-compressed public key + +Returns **Promise** + +#### Usage + +```ts +await signer.getPublicKey(); +// +``` + +### getPrivateKey + +Returns the signer's actual raw private key + +Returns **Promise** + +#### Usage + +```ts +await signer.getPrivateKey(); +// +``` + +### signData + +Generates a data signature for arbitrary input + +#### Parameters + +* `data` **Uint8Array** the data to be signed + +Returns **Promise** + +#### Usage + +```ts +const dataToSign: Uint8Array = // ... + + await signer.signData(dataToSign); +// +``` + +### verifySignature + +Verifies if the signature matches the provided raw data + +#### Parameters + +* `data` **Uint8Array** the raw data (not-hashed) +* `signature` **Uint8Array** the hashed-data signature + +Returns **Promise** + +#### Usage + +```ts +const signedData: Uint8Array = // ... +const rawData: Uint8Array = // ... + + await signer.verifySignature(rawData, signedData); +// +``` + diff --git a/docs/reference/tm2-js-client/getting-started.md b/docs/reference/tm2-js-client/getting-started.md new file mode 100644 index 00000000000..4a20ce2684f --- /dev/null +++ b/docs/reference/tm2-js-client/getting-started.md @@ -0,0 +1,65 @@ +--- +id: tm2-js-getting-started +--- + +# Getting Started + +[@gnolang/tm2-js-client](https://github.com/gnolang/tm2-js-client) is a JavaScript/TypeScript client implementation for +Tendermint2-based chains. It is designed to make it +easy for developers to interact with TM2 chains, providing a simplified API for account and transaction management. By +doing all the heavy lifting behind the scenes, `@gnolang/tm2-js-client` enables developers to focus on what really +matters - +building their dApps. + +## Key Features + +- JSON-RPC and WebSocket client support via a `Provider` +- Simple account and transaction management API with a `Wallet` +- Designed for easy extension for custom TM2 chains, such as [Gnoland](https://gno.land) + +## Installation + +To install `@gnolang/tm2-js-client`, use your preferred package manager: + +```bash +yarn add @gnolang/tm2-js-client +``` + +```bash +npm install @gnolang/tm2-js-client +``` + +## Common Terminology + +### Provider + +A `Provider` is an interface that abstracts the interaction with the Tendermint2 chain, making it easier for users to +communicate with it. Rather than requiring users to understand which endpoints are exposed, what their return types are, +and how they are parsed, the `Provider` abstraction handles all of this behind the scenes. It exposes useful API methods +that users can use and expects concrete types in return. + +Currently, the `@gnolang/tm2-js-client` package provides support for two Provider implementations: + +- `JSON-RPC Provider`: executes each call as a separate HTTP RPC call. +- `WS Provider`: executes each call through an active WebSocket connection, which requires closing when not needed + anymore. + +### Signer + +A `Signer` is an interface that abstracts the interaction with a single Secp256k1 key pair. It exposes methods for +signing data, verifying signatures, and getting metadata associated with the key pair, such as the address. + +Currently, the `@gnolang/tm2-js-client` package provides support for two `Signer` implementations: + +- `Key`: a signer that is based on a raw Secp256k1 key pair. +- `Ledger`: a signer that is based on a Ledger device, with all interaction flowing through the user's device. + +### Wallet + +A `Wallet` is a user-facing API that is used to interact with an account. A `Wallet` instance is tied to a single key +pair and essentially wraps the given `Provider` for that specific account. + +A wallet can be generated from a randomly generated seed, a private key, or instantiated using a Ledger device. + +Using the `Wallet`, users can easily interact with the Tendermint2 chain using their account without having to worry +about account management. diff --git a/docs/reference/tm2-js-client/wallet.md b/docs/reference/tm2-js-client/wallet.md new file mode 100644 index 00000000000..8a6943dfc9a --- /dev/null +++ b/docs/reference/tm2-js-client/wallet.md @@ -0,0 +1,275 @@ +--- +id: tm2-js-wallet +--- + +# Wallet + +A `Wallet` is a user-facing API that is used to interact with an account. A `Wallet` instance is tied to a single key +pair and essentially wraps the given `Provider` for that specific account. + +A wallet can be generated from a randomly generated seed, a private key, or instantiated using a Ledger device. + +Using the `Wallet`, users can easily interact with the Tendermint2 chain using their account without having to worry +about account management. + +## Initialization + +### createRandom + +Generates a private key-based wallet, using a random seed + +#### Parameters + +* `options?` **AccountWalletOption** the account options + +Returns **Promise** + +#### Usage + +```ts +const wallet: Wallet = await Wallet.createRandom(); +// random wallet created +``` + +### fromMnemonic + +Generates a bip39 mnemonic-based wallet + +#### Parameters + +* `mnemonic` **string** the bip39 mnemonic +* `options?` **CreateWalletOptions** the wallet generation options + +Returns **Promise** + +#### Usage + +```ts +const mnemonic: string = // ... +const wallet: Wallet = await Wallet.fromMnemonic(mnemonic); +// wallet created from mnemonic +``` + +### fromPrivateKey + +Generates a private key-based wallet + +#### Parameters + +* `privateKey` **string** the private key +* `options?` **AccountWalletOption** the wallet generation options + +Returns **Promise** + +#### Usage + +```ts +// Generate the private key from somewhere +const {publicKey, privateKey} = await generateKeyPair( + entropyToMnemonic(generateEntropy()), + index ? index : 0 +); + +const wallet: Wallet = await Wallet.fromPrivateKey(privateKey); +// wallet created from private key +``` + +### fromLedger + +Creates a Ledger-based wallet + +#### Parameters + +* `connector` **LedgerConnector** the Ledger device connector +* `options?` **CreateWalletOptions** the wallet generation options + +Returns **Wallet** + +#### Usage + +```ts +const connector: LedgerConnector = // ... + +const wallet: Wallet = await Wallet.fromLedger(connector); +// wallet created from Ledger device connection +``` + +## Provider Methods + +### connect + +Connects the wallet to the specified Provider + +#### Parameters + +* `provider` **Provider** the active Provider, if any + +#### Usage + +```ts +const provider: Provider = // ... + + wallet.connect(provider); +// Provider connected to Wallet +``` + +### getProvider + +Returns the connected provider, if any + +Returns **Provider** + +#### Usage + +```ts +wallet.getProvider(); +// connected provider, if any (undefined if not) +``` + +## Account Methods + +### getAddress + +Fetches the address associated with the wallet + +Returns **Promise** + +#### Usage + +```ts +await wallet.getAddress(); +// "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" +``` + +### getSequence + +Fetches the account sequence for the wallet + +#### Parameters + +* `height` **number** the block height (optional, default `latest`) + +#### Usage + +```ts +await wallet.getSequence(); +// 42 +``` + +Returns **Promise** + +### getAccountNumber + +Fetches the account number for the wallet. Errors out if the +account is not initialized + +#### Parameters + +* `height` **number** the block height (optional, default `latest`) + +Returns **Promise** + +#### Usage + +```ts +await wallet.getAccountNumber(); +// 10 +``` + +### getBalance + +Fetches the account balance for the specific denomination + +#### Parameters + +* `denomination` **string** the fund denomination (optional, default `ugnot`) + +Returns **Promise** + +#### Usage + +```ts +await wallet.getBalance('ugnot'); +// 5000 +``` + +### getGasPrice + +Fetches the current (recommended) average gas price + +Returns **Promise** + +#### Usage + +```ts +await wallet.getGasPrice(); +// 63000 +``` + +### estimateGas + +Estimates the gas limit for the transaction + +#### Parameters + +* `tx` **Tx** the transaction that needs estimating + +Returns **Promise** + +#### Usage + +```ts +const tx: Tx = // ... + + await wallet.estimateGas(tx); +// 120000 +``` + +### signTransaction + +Generates a transaction signature, and appends it to the transaction + +#### Parameters + +* `tx` **Tx** the transaction to be signed + +Returns **Promise** + +#### Usage + +```ts +const tx: Tx = // ... + + await wallet.signTransaction(tx); +// transaction with appended signature +``` + +### sendTransaction + +Signs and sends the transaction. Returns the transaction hash (base-64) + +#### Parameters + +* `tx` **Tx** the unsigned transaction + +Returns **Promise** + +#### Usage + +```ts +await wallet.sendTransaction(tx); +// returns the transaction hash +``` + +### getSigner + +Returns the associated signer + +Returns **Signer** + +#### Usage + +```ts +wallet.getSigner(tx); +// Signer instance +``` diff --git a/docs/testing_guide.md b/docs/testing-guide.md similarity index 100% rename from docs/testing_guide.md rename to docs/testing-guide.md diff --git a/examples/Makefile b/examples/Makefile index 5075df198ac..9b628e01ce5 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -19,7 +19,7 @@ build: precompile .PHONY: test test: - go run ../gnovm/cmd/gno test --verbose . + go run ../gnovm/cmd/gno test --verbose ./... .PHONY: lint lint: @@ -27,7 +27,7 @@ lint: .PHONY: test.sync test.sync: - go run ../gnovm/cmd/gno test --verbose --update-golden-tests . + go run ../gnovm/cmd/gno test --verbose --update-golden-tests ./... .PHONY: clean clean: @@ -37,3 +37,7 @@ clean: GOFMT_FLAGS ?= -w fmt: go run -modfile ../misc/devdeps/go.mod mvdan.cc/gofumpt $(GOFMT_FLAGS) `find . -name "*.gno"` + +.PHONY: tidy +tidy: + find . -name "gno.mod" -execdir go run github.com/gnolang/gno/gnovm/cmd/gno mod tidy \; diff --git a/examples/gno.land/p/demo/acl/gno.mod b/examples/gno.land/p/demo/acl/gno.mod index 2aabe3a5645..176cde637bd 100644 --- a/examples/gno.land/p/demo/acl/gno.mod +++ b/examples/gno.land/p/demo/acl/gno.mod @@ -1,6 +1,6 @@ module gno.land/p/demo/acl require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/p/demo/testutils" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/blog/blog.gno b/examples/gno.land/p/demo/blog/blog.gno index 62103b52885..1cf37b7ad3a 100644 --- a/examples/gno.land/p/demo/blog/blog.gno +++ b/examples/gno.land/p/demo/blog/blog.gno @@ -13,13 +13,28 @@ import ( ) type Blog struct { - Title string - Prefix string // i.e. r/gnoland/blog: - Posts avl.Tree // slug -> Post + Title string + Prefix string // i.e. r/gnoland/blog: + Posts avl.Tree // slug -> Post + NoBreadcrumb bool +} + +func (b Blog) RenderLastPostsWidget(limit int) string { + output := "" + i := 0 + b.Posts.Iterate("", "", func(key string, value interface{}) bool { + p := value.(*Post) + output += ufmt.Sprintf("- [%s](%s)\n", p.Title, p.URL()) + i++ + return i >= limit + }) + return output } func (b Blog) RenderHome(res *mux.ResponseWriter, req *mux.Request) { - res.Write(breadcrumb([]string{b.Title})) + if !b.NoBreadcrumb { + res.Write(breadcrumb([]string{b.Title})) + } if b.Posts.Size() == 0 { res.Write("No posts.") @@ -47,12 +62,14 @@ func (b Blog) RenderPost(res *mux.ResponseWriter, req *mux.Request) { } p := post.(*Post) - breadStr := breadcrumb([]string{ - ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), - "p", - p.Title, - }) - res.Write(breadStr) + if !b.NoBreadcrumb { + breadStr := breadcrumb([]string{ + ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), + "p", + p.Title, + }) + res.Write(breadStr) + } // output += ufmt.Sprintf("## [%s](%s)\n", p.Title, p.URL()) res.Write(p.Body + "\n\n") @@ -75,12 +92,14 @@ func (b Blog) RenderTag(res *mux.ResponseWriter, req *mux.Request) { return } - breadStr := breadcrumb([]string{ - ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), - "t", - slug, - }) - res.Write(breadStr) + if !b.NoBreadcrumb { + breadStr := breadcrumb([]string{ + ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix), + "t", + slug, + }) + res.Write(breadStr) + } nb := 0 b.Posts.Iterate("", "", func(key string, value interface{}) bool { diff --git a/examples/gno.land/p/demo/blog/gno.mod b/examples/gno.land/p/demo/blog/gno.mod index c8437af1732..65f58e7a0f6 100644 --- a/examples/gno.land/p/demo/blog/gno.mod +++ b/examples/gno.land/p/demo/blog/gno.mod @@ -1,7 +1,7 @@ module gno.land/p/demo/blog require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/p/demo/ufmt" v0.0.0-latest - "gno.land/p/demo/mux" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/mux v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/dom/gno.mod b/examples/gno.land/p/demo/dom/gno.mod index e7d0e5a9e75..83ca827cf66 100644 --- a/examples/gno.land/p/demo/dom/gno.mod +++ b/examples/gno.land/p/demo/dom/gno.mod @@ -1,5 +1,3 @@ module gno.land/p/demo/dom -require ( - "gno.land/p/demo/avl" v0.0.0-latest -) +require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/demo/flow/gno.mod b/examples/gno.land/p/demo/flow/gno.mod index 4a4d4fb4d82..5adddbfe021 100644 --- a/examples/gno.land/p/demo/flow/gno.mod +++ b/examples/gno.land/p/demo/flow/gno.mod @@ -1 +1 @@ -module "gno.land/p/demo/flow" +module gno.land/p/demo/flow diff --git a/examples/gno.land/p/demo/gnode/gno.mod b/examples/gno.land/p/demo/gnode/gno.mod index e922821f7fd..a93c2051830 100644 --- a/examples/gno.land/p/demo/gnode/gno.mod +++ b/examples/gno.land/p/demo/gnode/gno.mod @@ -1 +1 @@ -module "gno.land/p/demo/gnode" +module gno.land/p/demo/gnode diff --git a/examples/gno.land/p/demo/grc/exts/vault/gno.mod b/examples/gno.land/p/demo/grc/exts/vault/gno.mod index 8b4d4524366..2720bf09d95 100644 --- a/examples/gno.land/p/demo/grc/exts/vault/gno.mod +++ b/examples/gno.land/p/demo/grc/exts/vault/gno.mod @@ -1,6 +1,6 @@ module gno.land/p/demo/grc/exts/vault require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/p/demo/grc/grc20" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/grc/grc20 v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/grc/grc1155/gno.mod b/examples/gno.land/p/demo/grc/grc1155/gno.mod index 33a8e55be71..0b2b85d8e86 100644 --- a/examples/gno.land/p/demo/grc/grc1155/gno.mod +++ b/examples/gno.land/p/demo/grc/grc1155/gno.mod @@ -1,7 +1,7 @@ module gno.land/p/demo/grc/grc1155 require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/r/demo/users" v0.0.0-latest - "gno.land/p/demo/ufmt" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/grc/grc1155/util.gno b/examples/gno.land/p/demo/grc/grc1155/util.gno index 72a5d73561c..2c6452a1066 100644 --- a/examples/gno.land/p/demo/grc/grc1155/util.gno +++ b/examples/gno.land/p/demo/grc/grc1155/util.gno @@ -7,7 +7,7 @@ import ( const zeroAddress std.Address = "" func isValidAddress(addr std.Address) bool { - if addr.String() == "" { + if !addr.IsValid() { return false } return true diff --git a/examples/gno.land/p/demo/grc/grc20/gno.mod b/examples/gno.land/p/demo/grc/grc20/gno.mod index 5e6e13f834c..fd80766a956 100644 --- a/examples/gno.land/p/demo/grc/grc20/gno.mod +++ b/examples/gno.land/p/demo/grc/grc20/gno.mod @@ -1,7 +1,7 @@ module gno.land/p/demo/grc/grc20 require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/p/demo/ufmt" v0.0.0-latest - "gno.land/p/demo/grc/exts" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/grc/exts v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/grc/grc20/util.gno b/examples/gno.land/p/demo/grc/grc20/util.gno index a70edf421ba..2892b036bbd 100644 --- a/examples/gno.land/p/demo/grc/grc20/util.gno +++ b/examples/gno.land/p/demo/grc/grc20/util.gno @@ -5,7 +5,7 @@ import "std" const zeroAddress = std.Address("") func checkIsValidAddress(addr std.Address) error { - if addr.String() == "" { + if !addr.IsValid() { return ErrInvalidAddress } return nil diff --git a/examples/gno.land/p/demo/grc/grc721/gno.mod b/examples/gno.land/p/demo/grc/grc721/gno.mod index 229fc3f739c..ea8c9c9e52e 100644 --- a/examples/gno.land/p/demo/grc/grc721/gno.mod +++ b/examples/gno.land/p/demo/grc/grc721/gno.mod @@ -1,7 +1,7 @@ module gno.land/p/demo/grc/grc721 require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/r/demo/users" v0.0.0-latest - "gno.land/p/demo/ufmt" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/grc/grc721/util.gno b/examples/gno.land/p/demo/grc/grc721/util.gno index f82ba98194a..bb6bf24d984 100644 --- a/examples/gno.land/p/demo/grc/grc721/util.gno +++ b/examples/gno.land/p/demo/grc/grc721/util.gno @@ -7,7 +7,7 @@ import ( var zeroAddress = std.Address("") func isValidAddress(addr std.Address) error { - if addr.String() == "" { + if !addr.IsValid() { return ErrInvalidAddress } return nil diff --git a/examples/gno.land/p/demo/grc/grc777/gno.mod b/examples/gno.land/p/demo/grc/grc777/gno.mod index 2fddce3f8f8..9fbf2f2b7cd 100644 --- a/examples/gno.land/p/demo/grc/grc777/gno.mod +++ b/examples/gno.land/p/demo/grc/grc777/gno.mod @@ -1,5 +1,3 @@ module gno.land/p/demo/grc/grc777 -require ( - "gno.land/p/demo/grc/exts" v0.0.0-latest -) +require gno.land/p/demo/grc/exts v0.0.0-latest diff --git a/examples/gno.land/p/demo/groups/gno.mod b/examples/gno.land/p/demo/groups/gno.mod index b52ad8b05b1..0e9f7cf2a7c 100644 --- a/examples/gno.land/p/demo/groups/gno.mod +++ b/examples/gno.land/p/demo/groups/gno.mod @@ -1,7 +1,6 @@ module gno.land/p/demo/groups require ( - "gno.land/r/demo/boards" v0.0.0-latest - "gno.land/p/demo/maths" v0.0.0-latest - "gno.land/p/demo/testutils" v0.0.0-latest + gno.land/p/demo/maths v0.0.0-latest + gno.land/r/demo/boards v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/math_eval/int32/gno.mod b/examples/gno.land/p/demo/math_eval/int32/gno.mod index 9a1e634447c..de57497a699 100644 --- a/examples/gno.land/p/demo/math_eval/int32/gno.mod +++ b/examples/gno.land/p/demo/math_eval/int32/gno.mod @@ -1,5 +1,3 @@ module gno.land/p/demo/math_eval/int32 -require ( - "gno.land/p/demo/ufmt" v0.0.0-latest -) +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/microblog/gno.mod b/examples/gno.land/p/demo/microblog/gno.mod index fe19b89f777..5964679efa6 100644 --- a/examples/gno.land/p/demo/microblog/gno.mod +++ b/examples/gno.land/p/demo/microblog/gno.mod @@ -1,7 +1,8 @@ module gno.land/p/demo/microblog require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/p/demo/ufmt" v0.0.0-latest - "gno.land/r/demo/users" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/mux/gno.mod b/examples/gno.land/p/demo/mux/gno.mod index 13e4736bea8..972a531e14c 100644 --- a/examples/gno.land/p/demo/mux/gno.mod +++ b/examples/gno.land/p/demo/mux/gno.mod @@ -1 +1 @@ -module "gno.land/p/demo/mux" +module gno.land/p/demo/mux diff --git a/examples/gno.land/p/demo/ownable/errors.gno b/examples/gno.land/p/demo/ownable/errors.gno new file mode 100644 index 00000000000..ffbf6ab3f6f --- /dev/null +++ b/examples/gno.land/p/demo/ownable/errors.gno @@ -0,0 +1,8 @@ +package ownable + +import "errors" + +var ( + ErrUnauthorized = errors.New("unauthorized; caller is not owner") + ErrInvalidAddress = errors.New("new owner address is invalid") +) diff --git a/examples/gno.land/p/demo/ownable/gno.mod b/examples/gno.land/p/demo/ownable/gno.mod new file mode 100644 index 00000000000..9a9abb1e661 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/ownable diff --git a/examples/gno.land/p/demo/ownable/ownable.gno b/examples/gno.land/p/demo/ownable/ownable.gno new file mode 100644 index 00000000000..7f2eac008e1 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/ownable.gno @@ -0,0 +1,57 @@ +package ownable + +import ( + "std" +) + +// Ownable is meant to be used as a top-level object to make your contract ownable OR +// being embedded in a Gno object to manage per-object ownership. +type Ownable struct { + owner std.Address +} + +func New() *Ownable { + return &Ownable{ + owner: std.GetOrigCaller(), + } +} + +// TransferOwnership transfers ownership of the Ownable struct to a new address +func (o *Ownable) TransferOwnership(newOwner std.Address) error { + err := o.CallerIsOwner() + if err != nil { + return err + } + + if !newOwner.IsValid() { + return ErrInvalidAddress + } + + o.owner = newOwner + return nil +} + +// DropOwnership removes the owner, effectively disabling any owner-related actions +// Top-level usage: disables all only-owner actions/functions, +// Embedded usage: behaves like a burn functionality, removing the owner from the struct +func (o *Ownable) DropOwnership() error { + err := o.CallerIsOwner() + if err != nil { + return err + } + + o.owner = "" + return nil +} + +// CallerIsOwner checks if the caller of the function is the Realm's owner +func (o *Ownable) CallerIsOwner() error { + if std.GetOrigCaller() == o.owner { + return nil + } + return ErrUnauthorized +} + +func (o *Ownable) Owner() std.Address { + return o.owner +} diff --git a/examples/gno.land/p/demo/ownable/ownable_test.gno b/examples/gno.land/p/demo/ownable/ownable_test.gno new file mode 100644 index 00000000000..f725795fd47 --- /dev/null +++ b/examples/gno.land/p/demo/ownable/ownable_test.gno @@ -0,0 +1,113 @@ +package ownable + +import ( + "std" + "testing" +) + +var ( + firstCaller = std.Address("g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de") + secondCaller = std.Address("g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa") +) + +func TestNew(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + result := New() + if firstCaller != result.owner { + t.Fatalf("Expected %s, got: %s\n", firstCaller, result.owner) + } +} + +func TestOwner(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + result := New() + resultOwner := result.Owner() + + expected := firstCaller + if resultOwner != expected { + t.Fatalf("Expected %s, got: %s\n", expected, result) + } +} + +func TestTransferOwnership(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + o := New() + + err := o.TransferOwnership(secondCaller) + if err != nil { + t.Fatalf("TransferOwnership failed, %v", err) + } + + result := o.Owner() + if secondCaller != result { + t.Fatalf("Expected: %s, got: %s\n", secondCaller, result) + } +} + +func TestCallerIsOwner(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + o := New() + unauthorizedCaller := secondCaller + + std.TestSetOrigCaller(unauthorizedCaller) + + err := o.CallerIsOwner() + if err == nil { + t.Fatalf("Expected %s to not be owner\n", unauthorizedCaller) + } +} + +func TestDropOwnership(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + o := New() + + err := o.DropOwnership() + if err != nil { + t.Fatalf("DropOwnership failed, %v", err) + } + + owner := o.Owner() + if owner != "" { + t.Fatalf("Expected owner to be empty, not %s\n", owner) + } +} + +// Errors + +func TestErrUnauthorized(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + o := New() + + std.TestSetOrigCaller(secondCaller) + + err := o.TransferOwnership(firstCaller) + if err != ErrUnauthorized { + t.Fatalf("Should've been ErrUnauthorized, was %v", err) + } + + err = o.DropOwnership() + if err != ErrUnauthorized { + t.Fatalf("Should've been ErrUnauthorized, was %v", err) + } +} + +func TestErrInvalidAddress(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + o := New() + + err := o.TransferOwnership("") + if err != ErrInvalidAddress { + t.Fatalf("Should've been ErrInvalidAddress, was %v", err) + } + + err = o.TransferOwnership("10000000001000000000100000000010000000001000000000") + if err != ErrInvalidAddress { + t.Fatalf("Should've been ErrInvalidAddress, was %v", err) + } +} diff --git a/examples/gno.land/p/demo/pausable/gno.mod b/examples/gno.land/p/demo/pausable/gno.mod new file mode 100644 index 00000000000..08c7a4f7e5f --- /dev/null +++ b/examples/gno.land/p/demo/pausable/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/pausable + +require gno.land/p/demo/ownable v0.0.0-latest diff --git a/examples/gno.land/p/demo/pausable/pausable.gno b/examples/gno.land/p/demo/pausable/pausable.gno new file mode 100644 index 00000000000..eae3456ba61 --- /dev/null +++ b/examples/gno.land/p/demo/pausable/pausable.gno @@ -0,0 +1,49 @@ +package pausable + +import "gno.land/p/demo/ownable" + +type Pausable struct { + *ownable.Ownable + paused bool +} + +// New returns a new Pausable struct with non-paused state as default +func New() *Pausable { + return &Pausable{ + Ownable: ownable.New(), + paused: false, + } +} + +// NewFromOwnable is the same as New, but with a pre-existing top-level ownable +func NewFromOwnable(ownable *ownable.Ownable) *Pausable { + return &Pausable{ + Ownable: ownable, + paused: false, + } +} + +// IsPaused checks if Pausable is paused +func (p Pausable) IsPaused() bool { + return p.paused +} + +// Pause sets the state of Pausable to true, meaning all pausable functions are paused +func (p *Pausable) Pause() error { + if err := p.CallerIsOwner(); err != nil { + return err + } + + p.paused = true + return nil +} + +// Unpause sets the state of Pausable to false, meaning all pausable functions are resumed +func (p *Pausable) Unpause() error { + if err := p.CallerIsOwner(); err != nil { + return err + } + + p.paused = false + return nil +} diff --git a/examples/gno.land/p/demo/pausable/pausable_test.gno b/examples/gno.land/p/demo/pausable/pausable_test.gno new file mode 100644 index 00000000000..cc95c457573 --- /dev/null +++ b/examples/gno.land/p/demo/pausable/pausable_test.gno @@ -0,0 +1,77 @@ +package pausable + +import ( + "std" + "testing" + + "gno.land/p/demo/ownable" +) + +var ( + firstCaller = std.Address("g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de") + secondCaller = std.Address("g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa") +) + +func TestNew(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + result := New() + + if result.paused != false { + t.Fatalf("Expected result to be unpaused, got %t\n", result.paused) + } + + if result.Owner() != firstCaller { + t.Fatalf("Expected %s, got %s\n", firstCaller, result.Owner()) + } +} + +func TestNewFromOwnable(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + o := ownable.New() + + std.TestSetOrigCaller(secondCaller) + result := NewFromOwnable(o) + + if result.Owner() != firstCaller { + t.Fatalf("Expected %s, got %s\n", firstCaller, result.Owner()) + } +} + +func TestSetUnpaused(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + result := New() + result.Unpause() + + if result.IsPaused() { + t.Fatalf("Expected result to be unpaused, got %t\n", result.IsPaused()) + } +} + +func TestSetPaused(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + result := New() + result.Pause() + + if !result.IsPaused() { + t.Fatalf("Expected result to be paused, got %t\n", result.IsPaused()) + } +} + +func TestIsPaused(t *testing.T) { + std.TestSetOrigCaller(firstCaller) + + result := New() + + if result.IsPaused() { + t.Fatalf("Expected result to be unpaused, got %t\n", result.IsPaused()) + } + + result.Pause() + + if !result.IsPaused() { + t.Fatalf("Expected result to be paused, got %t\n", result.IsPaused()) + } +} diff --git a/examples/gno.land/p/demo/rand/gno.mod b/examples/gno.land/p/demo/rand/gno.mod index 66082ea873e..098af152648 100644 --- a/examples/gno.land/p/demo/rand/gno.mod +++ b/examples/gno.land/p/demo/rand/gno.mod @@ -1,3 +1,3 @@ // Draft -module gno.land/p/demo/rand +module gno.land/p/demo/rand diff --git a/examples/gno.land/p/demo/svg/gno.mod b/examples/gno.land/p/demo/svg/gno.mod index adb2acf1350..0af7ba0636d 100644 --- a/examples/gno.land/p/demo/svg/gno.mod +++ b/examples/gno.land/p/demo/svg/gno.mod @@ -1,5 +1,3 @@ module gno.land/p/demo/svg -require ( - "gno.land/p/demo/ufmt" v0.0.0-latest -) +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/tests/gno.mod b/examples/gno.land/p/demo/tests/gno.mod index 229c2f62d9c..5d80e106567 100644 --- a/examples/gno.land/p/demo/tests/gno.mod +++ b/examples/gno.land/p/demo/tests/gno.mod @@ -1,6 +1,6 @@ module gno.land/p/demo/tests require ( - "gno.land/p/demo/tests/subtests" v0.0.0-latest - "gno.land/r/demo/tests" v0.0.0-latest + gno.land/p/demo/tests/subtests v0.0.0-latest + gno.land/r/demo/tests v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/tests/subtests/gno.mod b/examples/gno.land/p/demo/tests/subtests/gno.mod index 26ec7c4879a..c8333722809 100644 --- a/examples/gno.land/p/demo/tests/subtests/gno.mod +++ b/examples/gno.land/p/demo/tests/subtests/gno.mod @@ -1,4 +1,4 @@ module gno.land/p/demo/tests/subtests -// TODO: this file should not exist. +// TODO: this file should not exist. // This is a temporary workaround. Until https://github.com/gnolang/gno/issues/852 diff --git a/examples/gno.land/p/demo/ui/gno.mod b/examples/gno.land/p/demo/ui/gno.mod index e71ee2d1ab1..41f5cb78d83 100644 --- a/examples/gno.land/p/demo/ui/gno.mod +++ b/examples/gno.land/p/demo/ui/gno.mod @@ -1 +1 @@ -module "gno.land/p/demo/ui" +module gno.land/p/demo/ui diff --git a/examples/gno.land/p/demo/ui/ui.gno b/examples/gno.land/p/demo/ui/ui.gno index efa185914a0..dd21d0510eb 100644 --- a/examples/gno.land/p/demo/ui/ui.gno +++ b/examples/gno.land/p/demo/ui/ui.gno @@ -1,6 +1,9 @@ package ui -import "strings" +import ( + "strconv" + "strings" +) type DOM struct { // metadata @@ -56,6 +59,17 @@ func (dom DOM) String() string { return output } +type Jumbotron []DomStringer + +func (j Jumbotron) String(dom DOM) string { + output := `
` + "\n\n" + for _, elem := range j { + output += elem.String(dom) + "\n" + } + output += `
` + "\n" + return output +} + // XXX: rename Element to Div? type Element []DomStringer @@ -88,6 +102,26 @@ func (b Breadcrumb) String(dom DOM) string { return output } +type Columns struct { + MaxWidth int + Columns []Element +} + +func (c *Columns) Append(elems ...Element) { + c.Columns = append(c.Columns, elems...) +} + +func (c Columns) String(dom DOM) string { + output := `
` + "\n" + for _, entry := range c.Columns { + output += `
` + "\n\n" + output += entry.String(dom) + output += "
\n" + } + output += "
\n" + return output +} + type Link struct { Text string Path string @@ -104,8 +138,14 @@ func (l Link) String(dom DOM) string { case l.Path != "" && l.URL != "": panic("a link should have a path or a URL, not both.") case l.Path != "": + if l.Text == "" { + l.Text = l.Path + } url = dom.Prefix + l.Path case l.URL != "": + if l.Text == "" { + l.Text = l.URL + } url = l.URL } @@ -151,6 +191,7 @@ type ( Italic string Code string Paragraph string + Quote string HR struct{} ) @@ -160,6 +201,7 @@ func (text H3) String(_ DOM) string { return "### " + string(text) + "\n" func (text H4) String(_ DOM) string { return "#### " + string(text) + "\n" } func (text H5) String(_ DOM) string { return "##### " + string(text) + "\n" } func (text H6) String(_ DOM) string { return "###### " + string(text) + "\n" } +func (text Quote) String(_ DOM) string { return "> " + string(text) + "\n" } func (text Bold) String(_ DOM) string { return "**" + string(text) + "**" } func (text Italic) String(_ DOM) string { return "_" + string(text) + "_" } func (text Paragraph) String(_ DOM) string { return "\n" + string(text) + "\n" } diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index 33d644206d6..bc17ee9df3b 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,6 +1,6 @@ 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 + 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/millipede/gno.mod b/examples/gno.land/r/demo/art/millipede/gno.mod index 10e3d2f1474..346e3a1673c 100644 --- a/examples/gno.land/r/demo/art/millipede/gno.mod +++ b/examples/gno.land/r/demo/art/millipede/gno.mod @@ -1,5 +1,3 @@ module gno.land/r/demo/art/millipede -require ( - "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/boards/gno.mod b/examples/gno.land/r/demo/boards/gno.mod index 882d97fec83..434ad019883 100644 --- a/examples/gno.land/r/demo/boards/gno.mod +++ b/examples/gno.land/r/demo/boards/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/demo/boards require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/r/demo/users" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/foo1155/gno.mod b/examples/gno.land/r/demo/foo1155/gno.mod index 07c05ff5ef3..6fdf18a1658 100644 --- a/examples/gno.land/r/demo/foo1155/gno.mod +++ b/examples/gno.land/r/demo/foo1155/gno.mod @@ -1,7 +1,7 @@ module gno.land/r/demo/foo1155 require ( - "gno.land/p/demo/ufmt" v0.0.0-latest - "gno.land/p/demo/grc/grc1155" v0.0.0-latest - "gno.land/r/demo/users" v0.0.0-latest + gno.land/p/demo/grc/grc1155 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/foo20/gno.mod b/examples/gno.land/r/demo/foo20/gno.mod index 1dbe9e01e4f..516690ee66c 100644 --- a/examples/gno.land/r/demo/foo20/gno.mod +++ b/examples/gno.land/r/demo/foo20/gno.mod @@ -1,7 +1,7 @@ module gno.land/r/demo/foo20 require ( - "gno.land/p/demo/ufmt" v0.0.0-latest - "gno.land/p/demo/grc/grc20" v0.0.0-latest - "gno.land/r/demo/users" v0.0.0-latest + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/foo721/gno.mod b/examples/gno.land/r/demo/foo721/gno.mod index b34ffd2b3fe..46c19e6ae55 100644 --- a/examples/gno.land/r/demo/foo721/gno.mod +++ b/examples/gno.land/r/demo/foo721/gno.mod @@ -1,7 +1,7 @@ module gno.land/r/demo/foo721 require ( - "gno.land/p/demo/ufmt" v0.0.0-latest - "gno.land/p/demo/grc/grc721" v0.0.0-latest - "gno.land/r/demo/users" v0.0.0-latest + gno.land/p/demo/grc/grc721 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/groups/README.md b/examples/gno.land/r/demo/groups/README.md index 1db5ae56b51..ecdd5065903 100644 --- a/examples/gno.land/r/demo/groups/README.md +++ b/examples/gno.land/r/demo/groups/README.md @@ -8,7 +8,7 @@ ### - create group - ./build/gnokey maketx call -func "CreateGroup" -args "dao_trinity_ngo" -gas-fee "1000000ugnot" -gas-wanted 4000000 -broadcast true -chainid dev -remote 0.0.0.0:26657 -pkgpath "gno.land/r/demo/groups" test1 + ./build/gnokey maketx call -func "CreateGroup" -args "dao_trinity_ngo" -gas-fee "1000000ugnot" -gas-wanted 4000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath "gno.land/r/demo/groups" test1 ### - add member diff --git a/examples/gno.land/r/demo/groups/gno.mod b/examples/gno.land/r/demo/groups/gno.mod index d97acbecc7a..fc6756e13e2 100644 --- a/examples/gno.land/r/demo/groups/gno.mod +++ b/examples/gno.land/r/demo/groups/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/demo/groups require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/r/demo/users" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/keystore/gno.mod b/examples/gno.land/r/demo/keystore/gno.mod index 88d59c9ccd6..af0b907c259 100644 --- a/examples/gno.land/r/demo/keystore/gno.mod +++ b/examples/gno.land/r/demo/keystore/gno.mod @@ -1,6 +1,7 @@ module gno.land/r/demo/keystore require ( - "gno.land/p/demo/ufmt" v0.0.0-latest - "gno.land/p/demo/avl" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/math_eval/gno.mod b/examples/gno.land/r/demo/math_eval/gno.mod index 69d4e8c459b..0e3fcfe6e9b 100644 --- a/examples/gno.land/r/demo/math_eval/gno.mod +++ b/examples/gno.land/r/demo/math_eval/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/demo/math_eval require ( - "gno.land/p/demo/ufmt" v0.0.0-latest - "gno.land/p/demo/math_eval/int32" v0.0.0-latest + gno.land/p/demo/math_eval/int32 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/microblog/gno.mod b/examples/gno.land/r/demo/microblog/gno.mod index 3d79a38d5fd..f496b1008ce 100644 --- a/examples/gno.land/r/demo/microblog/gno.mod +++ b/examples/gno.land/r/demo/microblog/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/demo/microblog require ( - "gno.land/p/demo/microblog" v0.0.0-latest - "gno.land/r/demo/users" v0.0.0-latest + gno.land/p/demo/microblog v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/nft/gno.mod b/examples/gno.land/r/demo/nft/gno.mod index 2fefdbd1907..89e0055be51 100644 --- a/examples/gno.land/r/demo/nft/gno.mod +++ b/examples/gno.land/r/demo/nft/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/demo/nft require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/p/demo/grc/grc721" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/grc/grc721 v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/releases_example/gno.mod b/examples/gno.land/r/demo/releases_example/gno.mod index 85bcd07f232..22f640fe797 100644 --- a/examples/gno.land/r/demo/releases_example/gno.mod +++ b/examples/gno.land/r/demo/releases_example/gno.mod @@ -1,5 +1,3 @@ module gno.land/r/demo/releases_example -require ( - "gno.land/p/demo/releases" v0.0.0-latest -) +require gno.land/p/demo/releases v0.0.0-latest diff --git a/examples/gno.land/r/demo/tests/gno.mod b/examples/gno.land/r/demo/tests/gno.mod index 23dd3760157..9c5162f848e 100644 --- a/examples/gno.land/r/demo/tests/gno.mod +++ b/examples/gno.land/r/demo/tests/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/demo/tests require ( - "gno.land/p/demo/testutils" v0.0.0-latest - "gno.land/r/demo/tests/subtests" v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/r/demo/tests/subtests v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/tests/subtests/gno.mod b/examples/gno.land/r/demo/tests/subtests/gno.mod index 80db73b4c15..9f466ff77b9 100644 --- a/examples/gno.land/r/demo/tests/subtests/gno.mod +++ b/examples/gno.land/r/demo/tests/subtests/gno.mod @@ -1,4 +1,4 @@ module gno.land/r/demo/tests/subtests -// TODO: this file should not exist. +// TODO: this file should not exist. // This is a temporary workaround. Until https://github.com/gnolang/gno/issues/852 diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index fb49b2273ae..0094ad2ae35 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -44,7 +44,7 @@ func (t *TestRealmObject) Modify() { } //---------------------------------------- -// Test helpers to test a particualr realm bug. +// Test helpers to test a particular realm bug. type TestNode struct { Name string diff --git a/examples/gno.land/r/demo/tests_foo/gno.mod b/examples/gno.land/r/demo/tests_foo/gno.mod index b19d8a21de1..226271ae4b0 100644 --- a/examples/gno.land/r/demo/tests_foo/gno.mod +++ b/examples/gno.land/r/demo/tests_foo/gno.mod @@ -1,5 +1,3 @@ module gno.land/r/demo/tests_foo -require ( - "gno.land/r/demo/tests" v0.0.0-latest -) +require gno.land/r/demo/tests v0.0.0-latest diff --git a/examples/gno.land/r/demo/types/gno.mod b/examples/gno.land/r/demo/types/gno.mod index 5709668ef2a..0e86e5d5676 100644 --- a/examples/gno.land/r/demo/types/gno.mod +++ b/examples/gno.land/r/demo/types/gno.mod @@ -1,5 +1,3 @@ module gno.land/r/demo/types -require ( - "gno.land/p/demo/avl" v0.0.0-latest -) +require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/r/demo/ui/gno.mod b/examples/gno.land/r/demo/ui/gno.mod index 597c0f388a4..42be8cec3f0 100644 --- a/examples/gno.land/r/demo/ui/gno.mod +++ b/examples/gno.land/r/demo/ui/gno.mod @@ -1,5 +1,3 @@ module gno.land/r/demo/ui -require ( - "gno.land/p/demo/ui" v0.0.0-latest -) +require gno.land/p/demo/ui v0.0.0-latest diff --git a/examples/gno.land/r/demo/users/gno.mod b/examples/gno.land/r/demo/users/gno.mod index 055b5816871..edd20eb2721 100644 --- a/examples/gno.land/r/demo/users/gno.mod +++ b/examples/gno.land/r/demo/users/gno.mod @@ -1,6 +1,3 @@ module gno.land/r/demo/users -require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/p/demo/testutils" v0.0.0-latest -) +require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/r/gnoland/blog/gno.mod b/examples/gno.land/r/gnoland/blog/gno.mod index a8b4f3ceaa9..1d64238cdc8 100644 --- a/examples/gno.land/r/gnoland/blog/gno.mod +++ b/examples/gno.land/r/gnoland/blog/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/gnoland/blog require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/p/demo/blog" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/blog v0.0.0-latest ) diff --git a/examples/gno.land/r/gnoland/blog/gnoblog.gno b/examples/gno.land/r/gnoland/blog/gnoblog.gno index 2982ea88489..cad84507614 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog.gno @@ -23,3 +23,7 @@ func AddComment(postSlug, comment string) { func Render(path string) string { return b.Render(path) } + +func RenderLastPostsWidget(limit int) string { + return b.RenderLastPostsWidget(limit) +} diff --git a/examples/gno.land/r/gnoland/faucet/gno.mod b/examples/gno.land/r/gnoland/faucet/gno.mod index 75e85df326b..693b0e795cf 100644 --- a/examples/gno.land/r/gnoland/faucet/gno.mod +++ b/examples/gno.land/r/gnoland/faucet/gno.mod @@ -1,7 +1,7 @@ module gno.land/r/gnoland/faucet require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/p/demo/testutils" v0.0.0-latest - "gno.land/p/demo/ufmt" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest ) diff --git a/examples/gno.land/r/gnoland/home/gno.mod b/examples/gno.land/r/gnoland/home/gno.mod new file mode 100644 index 00000000000..2864958930c --- /dev/null +++ b/examples/gno.land/r/gnoland/home/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/gnoland/home + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/ui v0.0.0-latest + gno.land/r/gnoland/blog v0.0.0-latest +) diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno new file mode 100644 index 00000000000..5f2a5b9c4b5 --- /dev/null +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -0,0 +1,257 @@ +package home + +import ( + "std" + + "gno.land/p/demo/ufmt" + "gno.land/p/demo/ui" + blog "gno.land/r/gnoland/blog" +) + +// XXX: p/demo/ui API is crappy, we need to make it more idiomatic +// XXX: use an updatable block system to update content from a DAO +// XXX: var blocks avl.Tree + +func Render(_ string) string { + dom := ui.DOM{Prefix: "r/gnoland/home:"} + dom.Title = "Welcome to Gno.land" + + // body + dom.Body.Append(introSection()...) + dom.Body.Append(ui.Jumbotron(worxDAO())) + dom.Body.Append(packageStaffPicks()...) + dom.Body.Append(ui.HR{}) + dom.Body.Append( + ui.Columns{3, []ui.Element{ + lastBlogposts(4), + upcomingEvents(4), + lastContributions(4), + }}, + ) + dom.Body.Append(ui.Jumbotron(discoverLinks())) + + // footer + dom.Footer.Append( + ui.Columns{2, []ui.Element{ + socialLinks(), + quoteOfTheBlock(), + }}, + ) + + // Testnet disclaimer + dom.Footer.Append( + ui.HR{}, + ui.Bold("This is a testnet."), + ui.Text("Package names are not guaranteed to be available for production."), + ) + + return dom.String() +} + +func lastBlogposts(limit int) ui.Element { + posts := blog.RenderLastPostsWidget(limit) + return ui.Element{ + ui.H3("Last Blogposts"), + ui.Text(posts), + } +} + +func lastContributions(limit int) ui.Element { + return ui.Element{ + ui.H3("Last Contributions"), + ui.Text("TODO: import r/gh"), + ui.Link{Text: "#1134", URL: "https://github.com/gnolang/gno/pull/1134"}, + } +} + +func upcomingEvents(limit int) ui.Element { + return ui.Element{ + ui.H3("Upcoming Events"), + ui.Text("TODO: import r/gnoland/events"), + } +} + +func introSection() ui.Element { + return ui.Element{ + ui.H3("An interpretation of the Golang (Go) programming language for advanced developers and intrepid pioneers to build succinct, composable smart contracts for social coordination."), + ui.Paragraph("If you’re concerned about information censorship and want to contribute to the #GnoWorldOrder, follow our socials to find out how."), + ui.Paragraph("Gno.land is in building mode. If you want to help lay the foundations of a fairer and freer world through innovative ideas and exceptional code, join us today."), + } +} + +func worxDAO() ui.Element { + // WorxDAO + // XXX(manfred): please, let me finish a v0, then we can iterate + // highest level == highest responsibility + // teams are responsible for components they don't owne + // flag : realm maintainers VS facilitators + // teams + // committee of trustees to create the directory + // each directory is a name, has a parent and have groups + // homepage team - blocks aggregating events + // XXX: TODO + /*` + # Directory + + * gno.land (owned by group) + * + * gnovm + * gnolang (language) + * gnovm + - current challenges / concerns / issues + * tm2 + * amino + * + + ## Contributors + ``*/ + return ui.Element{ + ui.H3("WorxDAO (WIP)"), + ui.Text(`- A + - A1 + - A1A + - A1B + - A2 + - A3 + - A3A + - A3A1 +- B +- C`), + } +} + +func quoteOfTheBlock() ui.Element { + quotes := []string{ + "Gno is for Truth.", + "Gno is for Social Coordination.", + "Gno is _not only_ for DeFi.", + "Now, you Gno.", + "Come for the Go, Stay for the Gno.", + } + height := std.GetHeight() + idx := int(height) % len(quotes) + qotb := quotes[idx] + + return ui.Element{ + ui.H3(ufmt.Sprintf("Quote of the ~Day~Block#%d", height)), + ui.Quote(qotb), + } +} + +func socialLinks() ui.Element { + return ui.Element{ + ui.H3("Socials"), + ui.BulletList{ + // XXX: improve UI to support a nice GO api for such links + ui.Text("Check out our [community projects](https://github.com/gnolang/awesome-gno)"), + ui.Text("![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn)"), + ui.Text("![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland)"), + ui.Text("![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland)"), + ui.Text("![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland)"), + }, + } +} + +func packageStaffPicks() ui.Element { + // XXX: make it modifiable from a DAO + return ui.Element{ + ui.H3("Explore New Packages and Realms"), + ui.Columns{ + 3, + []ui.Element{ + { + ui.H4("r/gnoland"), + ui.BulletList{ + ui.Link{URL: "r/gnoland/blog"}, + ui.Link{URL: "r/gnoland/dao"}, + ui.Link{URL: "r/gnoland/faucet"}, + ui.Link{URL: "r/gnoland/home"}, + ui.Link{URL: "r/gnoland/pages"}, + }, + ui.H4("r/system"), + ui.BulletList{ + ui.Link{URL: "r/system/names"}, + ui.Link{URL: "r/system/rewards"}, + ui.Link{URL: "r/system/validators"}, + }, + }, { + ui.H4("r/demo"), + ui.BulletList{ + ui.Link{URL: "r/demo/boards"}, + ui.Link{URL: "r/demo/users"}, + ui.Link{URL: "r/demo/banktest"}, + ui.Link{URL: "r/demo/foo20"}, + ui.Link{URL: "r/demo/foo721"}, + ui.Link{URL: "r/demo/microblog"}, + ui.Link{URL: "r/demo/nft"}, + ui.Link{URL: "r/demo/types"}, + ui.Link{URL: "r/demo/art"}, + ui.Link{URL: "r/demo/groups"}, + ui.Text("..."), + }, + }, { + ui.H4("p/demo"), + ui.BulletList{ + ui.Link{URL: "p/demo/avl"}, + ui.Link{URL: "p/demo/blog"}, + ui.Link{URL: "p/demo/ui"}, + ui.Link{URL: "p/demo/ufmt"}, + ui.Link{URL: "p/demo/merkle"}, + ui.Link{URL: "p/demo/bf"}, + ui.Link{URL: "p/demo/flow"}, + ui.Link{URL: "p/demo/gnode"}, + ui.Link{URL: "p/demo/grc/grc20"}, + ui.Link{URL: "p/demo/grc/grc721"}, + ui.Text("..."), + }, + }, + }, + }, + } +} + +func discoverLinks() ui.Element { + return ui.Element{ + ui.Text(`
+
+ +### Learn about Gno.land + +- [About](/about) +- [GitHub](https://github.com/gnolang) +- [Subscribe](#subscribe) +- [Tokenomics (soon)](#) +- [Blog](/blog) +- [Events](/events) +- [Partners, Fund, Grants](/partners) + +
+ +
+ +### Build with Gnolang + +- [Gno dev with CLI (soon)](#) +- [Explore the Universe](/ecosystem) +- [Test in the browser (soon)](#) +- [About the Gnolang Language](/gnolang) +- [Docs/ Tutorials](https://github.com/gnolang) +- [Gno by example](https://gno-by-example.com/) +- [Getting started video (soon)](#) + +
+
+ +### Explore the universe + +- [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples) +- [Install Gno Key instructions](/r/demo/boards:testboard/5) +- [Testnets 3](https://test3.gno.land/) +- [Testnets 2](https://test2.gno.land/) +- [Explorer links(soon)](#) +- [Testnet Tokens (faucet)](https://test3.gno.land/faucet) + +
+
`), + } +} diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno new file mode 100644 index 00000000000..1fffc11792f --- /dev/null +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -0,0 +1,188 @@ +package main + +import "gno.land/r/gnoland/home" + +func main() { + println(home.Render("")) +} + +// Output: +// # Welcome to Gno.land +// +// ### An interpretation of the Golang (Go) programming language for advanced developers and intrepid pioneers to build succinct, composable smart contracts for social coordination. +// +// +// If you’re concerned about information censorship and want to contribute to the #GnoWorldOrder, follow our socials to find out how. +// +// +// Gno.land is in building mode. If you want to help lay the foundations of a fairer and freer world through innovative ideas and exceptional code, join us today. +// +//
+// +// ### WorxDAO (WIP) +// +// - A +// - A1 +// - A1A +// - A1B +// - A2 +// - A3 +// - A3A +// - A3A1 +// - B +// - C +//
+// +// ### Explore New Packages and Realms +// +//
+//
+// +// #### r/gnoland +// +// - [r/gnoland/blog](r/gnoland/blog) +// - [r/gnoland/dao](r/gnoland/dao) +// - [r/gnoland/faucet](r/gnoland/faucet) +// - [r/gnoland/home](r/gnoland/home) +// - [r/gnoland/pages](r/gnoland/pages) +// +// #### r/system +// +// - [r/system/names](r/system/names) +// - [r/system/rewards](r/system/rewards) +// - [r/system/validators](r/system/validators) +// +//
+//
+// +// #### r/demo +// +// - [r/demo/boards](r/demo/boards) +// - [r/demo/users](r/demo/users) +// - [r/demo/banktest](r/demo/banktest) +// - [r/demo/foo20](r/demo/foo20) +// - [r/demo/foo721](r/demo/foo721) +// - [r/demo/microblog](r/demo/microblog) +// - [r/demo/nft](r/demo/nft) +// - [r/demo/types](r/demo/types) +// - [r/demo/art](r/demo/art) +// - [r/demo/groups](r/demo/groups) +// - ... +// +//
+//
+// +// #### p/demo +// +// - [p/demo/avl](p/demo/avl) +// - [p/demo/blog](p/demo/blog) +// - [p/demo/ui](p/demo/ui) +// - [p/demo/ufmt](p/demo/ufmt) +// - [p/demo/merkle](p/demo/merkle) +// - [p/demo/bf](p/demo/bf) +// - [p/demo/flow](p/demo/flow) +// - [p/demo/gnode](p/demo/gnode) +// - [p/demo/grc/grc20](p/demo/grc/grc20) +// - [p/demo/grc/grc721](p/demo/grc/grc721) +// - ... +// +//
+//
+// +// +// --- +// +//
+//
+// +// ### Last Blogposts +// +// +//
+//
+// +// ### Upcoming Events +// +// TODO: import r/gnoland/events +//
+//
+// +// ### Last Contributions +// +// TODO: import r/gh +// [#1134](https://github.com/gnolang/gno/pull/1134) +//
+//
+// +//
+// +//
+//
+// +// ### Learn about Gno.land +// +// - [About](/about) +// - [GitHub](https://github.com/gnolang) +// - [Subscribe](#subscribe) +// - [Tokenomics (soon)](#) +// - [Blog](/blog) +// - [Events](/events) +// - [Partners, Fund, Grants](/partners) +// +//
+// +//
+// +// ### Build with Gnolang +// +// - [Gno dev with CLI (soon)](#) +// - [Explore the Universe](/ecosystem) +// - [Test in the browser (soon)](#) +// - [About the Gnolang Language](/gnolang) +// - [Docs/ Tutorials](https://github.com/gnolang) +// - [Gno by example](https://gno-by-example.com/) +// - [Getting started video (soon)](#) +// +//
+//
+// +// ### Explore the universe +// +// - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples) +// - [Install Gno Key instructions](/r/demo/boards:testboard/5) +// - [Testnets 3](https://test3.gno.land/) +// - [Testnets 2](https://test2.gno.land/) +// - [Explorer links(soon)](#) +// - [Testnet Tokens (faucet)](https://test3.gno.land/faucet) +// +//
+//
+//
+// +// +//
+//
+// +// ### Socials +// +// - Check out our [community projects](https://github.com/gnolang/awesome-gno) +// - ![Discord](static/img/ico-discord.svg) [Discord](https://discord.gg/S8nKUqwkPn) +// - ![Twitter](static/img/ico-twitter.svg) [Twitter](https://twitter.com/_gnoland) +// - ![Youtube](static/img/ico-youtube.svg) [Youtube](https://www.youtube.com/@_gnoland) +// - ![Telegram](static/img/ico-telegram.svg) [Telegram](https://t.me/gnoland) +// +//
+//
+// +// ### Quote of the ~Day~Block#123 +// +// > Now, you Gno. +// +//
+//
+// +// +// --- +// +// **This is a testnet.** +// Package names are not guaranteed to be available for production. diff --git a/examples/gno.land/r/gnoland/pages/gno.mod b/examples/gno.land/r/gnoland/pages/gno.mod index 0f5c4076509..31e9ad2c85b 100644 --- a/examples/gno.land/r/gnoland/pages/gno.mod +++ b/examples/gno.land/r/gnoland/pages/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/gnoland/pages require ( - "gno.land/p/demo/avl" v0.0.0-latest - "gno.land/p/demo/blog" v0.0.0-latest + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/blog v0.0.0-latest ) diff --git a/gno.land/cmd/gnoweb/pages/ABOUT.md b/examples/gno.land/r/gnoland/pages/page_about.gno similarity index 73% rename from gno.land/cmd/gnoweb/pages/ABOUT.md rename to examples/gno.land/r/gnoland/pages/page_about.gno index a5678a7349a..9aba4e39f76 100644 --- a/gno.land/cmd/gnoweb/pages/ABOUT.md +++ b/examples/gno.land/r/gnoland/pages/page_about.gno @@ -1,4 +1,10 @@ -# About Gno.land +package gnopages + +func init() { + path := "about" + title := "Gno.land Is A Platform To Write Smart Contracts In Gnolang (Gno)" + // XXX: description := "On Gno.land, developers write smart contracts and other blockchain apps using Gnolang (Gno) without learning a language that’s exclusive to a single ecosystem." + body := `# About Gno.land Gno.land is a platform to write smart contracts in Gnolang (Gno). Using an interpreted version of the general-purpose programming language Golang (Go), developers can write smart contracts and other blockchain apps without having to learn a language that’s exclusive to a single ecosystem. @@ -9,4 +15,6 @@ Proof of Contribution rewards contributors from technical and non-technical back This consensus mechanism also achieves higher security with fewer validators, optimizing resources for a greener, more sustainable, and enduring blockchain ecosystem. Any blockchain using Gnolang achieves succinctness, composability, expressivity, and completeness not found in any other smart contract platform. -By observing a minimal structure, the design can endure over time and challenge the regime of information censorship we’re living in today. +By observing a minimal structure, the design can endure over time and challenge the regime of information censorship we’re living in today.` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_ecosystem.gno b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno new file mode 100644 index 00000000000..68969c44529 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_ecosystem.gno @@ -0,0 +1,35 @@ +package gnopages + +func init() { + var ( + path = "ecosystem" + title = "Discover Gno.land Ecosystem Projects & Initiatives" + // XXX: description = "Dive further into the Gno.land ecosystem and discover the core infrastructure, projects, smart contracts, and tooling we’re building." + body = `# Gno Ecosystem + +## Gno.land Space + +For the best onboarding experience, head over to [Gno.land Space](https://www.gnoland.space/) open ecosystem. Here you can set up your Gno wallet, explore existing community-written Gno smart contracts (realms), and become part of our vibrant community by joining [Gno.land Discord](https://discord.com/invite/x76qK4ttHC). + +## Gno Studio (IDE) + +Gno IDE is a web-based application helping builders quickly spin up Gno realms and packages right on their browsers. Offering a smooth and intuitive UX for building on Gno, you’ll find multiple modes for customizability with all the features you’d expect from an IDE, such as auto compilation in the editor, debugging, and extensive testing capability. + +## Gnoscan + +Developed by the Onbloc team, Gnoscan is Gno.land’s blockchain explorer. Anyone can use Gnoscan to easily find information that resides on the Gno.land blockchain, such as wallet addresses, TX hashes, blocks, and contracts. Gnoscan makes our on-chain data easy to read and intuitive to discover. [Go to Gnoscan.](https://gnoscan.io/) + +## Adena + +Adena is a user-friendly non-custodial wallet for Gno.land. Open-source and developed by Onbloc, Adena currently powers all transactions on Gno.land, allowing gnomes to interact easily with the chain. With an emphasis on UX, Adena is built to handle millions of realms and tokens with a high-quality interface, support for NFTs and custom tokens, and seamless integration. [Get started here.](https://adena.app/) + +## Gnoswap + +Gnoswap is currently under development and led by the Onbloc team. Gnoswap will be the first DEX on Gno.land and is an automated market maker (AMM) protocol written in Gnolang that allows for permissionless token exchanges on the platform. + +## Gno.land Developer Portal + +Through the Gno.land Developer Portal, new developers can explore the exciting world of Gnolang (Gno), a novel programming language that powers the Gno.land blockchain. If you want to interact with Gno.land, start writing a realm, build a dApp, or even port a Solidity contract to a Gnolang realm, you’ll find the resources to [get started here](https://docs.onbloc.xyz/).` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_events.gno b/examples/gno.land/r/gnoland/pages/page_events.gno new file mode 100644 index 00000000000..18e7faeb3d3 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_events.gno @@ -0,0 +1,151 @@ +package gnopages + +func init() { + var ( + path = "events" + title = "Gno.land Core Team Attends Industry Events & Meetups" + // XXX: description = "If you’re interested in learning more about Gno.land, you can join us at major blockchain industry events throughout the year either in person or virtually." + body = `# Events + +If you’re interested in building web3 with us, catch up with Gno.land in person at one of our industry events. We’re looking to connect with developers and like-minded thinkers who can contribute to the growth of our platform. + +--- + +## Upcoming Events + +
+
+ +### EthCC + +- **Come Meet Us at our Booth** +- Paris, July 17 - 20, 2023 +- Manfred Touron + +[Learn more](https://www.ethcc.io/) + +
+
+ +### Nebular Summit Gno.land for Developers + +- Paris, July 24 - 25, 2023 +- Manfred Touron + +[Learn more](https://www.nebular.builders/) + +
+
+ +### GopherCon EU + +- **Come Meet Us at our Booth** +- Berlin, July 26 - 29, 2023 + +[Learn more](https://gophercon.eu/) + +
+ +
+ +### GopherCon US + +- **Come Meet Us at our Booth** +- San Diego, September 26 - 29, 2023 + +[Learn more](https://www.gophercon.com/) + +
+
+ +--- + +## Past Events + +
+ +
+ +### Eth Seoul + +- **The Evolution of Smart Contracts: A Journey into Gno.land** +- Seoul, June 3, 2023 +- Manfred Touron + +[Learn more](https://2023.ethseoul.org/) + +
+
+ +### BUIDL Asia + +- **Proof of Contribution in Gno.land** +- Seoul, June 6, 2023 +- Manfred Touron + +[Learn more](https://www.buidl.asia/) + +
+
+ +### Game Developer Conference + +- **Side Event: Web3 Gaming Apps Powered by Gno** +- San Francisco, Mach 23, 2023 +- Jae Kwon + +[Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c) + +
+
+ +### EthDenver + +- **Side Event: Discover Gno.land** +- Denver, Feb 24 - Mar 5, 2023 +- Jae Kwon + +[Watch the talk](https://www.youtube.com/watch?v=IJ0xel8lr4c) + +
+
+ +### Istanbul Blockchain Week + +- Istanbul, Nov 14 - 17, 2022 +- Manfred Touron + +[Watch the talk](https://www.youtube.com/watch?v=JX0gdWT0Cg4) + +
+
+ +### Web Summit Buckle Up and Build with Cosmos + +- Lisbon, Nov 1 - 4, 2022 +- Manfred Touron + +
+
+ +### Cosmoverse + +- Medallin, Sept 26 - 28, 2022 +- Manfred Touron + +[Watch the talk](https://www.youtube.com/watch?v=6s1zG7hgxMk) + +
+
+ +### Berlin Blockchain Week Buckle Up and Build with Cosmos + +- Berlin, Sept 11 - 18, 2022 + +[Watch the talk](https://www.youtube.com/watch?v=hCLErPgnavI) + +
+
` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_gnolang.gno b/examples/gno.land/r/gnoland/pages/page_gnolang.gno new file mode 100644 index 00000000000..f0c2bfe276d --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_gnolang.gno @@ -0,0 +1,43 @@ +package gnopages + +func init() { + var ( + path = "gnolang" + title = "Gnolang (Gno) Is a Complete Language for Blockchain" + // XXX: description = "Gnolang (Gno) is an interpretation of the popular Golang (Go) language for blockchain created by Tendermint and Cosmos founder Jae Kwon." + body = `# About the Gnolang, the Gno Language + +[Gnolang](https://github.com/gnolang/gno/blob/master/LICENSE.md) (Gno) is an interpretation of the widely-used Golang (Go) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same. + +Under the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, rather than bytecode as in many virtual machines such as Java, Python, or Wasm. This makes even the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM interpreter allows Gno to freeze and resume the program by persisting and loading the entire memory state. Gno is deterministic, auto-persisted, and auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the programmer doesn’t have to serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK). + +## How Gno Differs from Go + +![Gno and Go differences](static/img/gno-language/go-and-gno.jpg) + +The composable nature of Go/Gno allows for type-checked interactions between contracts, making Gno.land safer and more powerful, as well as operationally cheaper and faster. Smart contracts on Gno.land are light, simple, more focused, and easily interoperable—a network of interconnected contracts rather than siloed monoliths that limit interactions with other contracts. + +![Example of Gno code](static/img/gno-language/code-example.jpg) + +## Gno Inherits Go’s Built-in Security Features + +Go supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple users. + +Another major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common mistakes. + +## Gno vs Solidity + +The most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling and its EVM has a stack limit and computational inefficiencies. + +Solidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In addition, developers have to learn several languages if they want to understand the whole stack or work across different ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or Substrate) as every part of the stack is written in Gno. It’s easy for developers to understand the entire system just by studying a relatively small code base. + +## Gno Is Essential for the Wider Adoption of Web3 + +Gno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, enabling remote function calls as local, and providing automatic and independent per-realm state persistence. + +Using Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive way while preserving localism, and the language specification is simple, successfully balancing practicality and minimalism. + +The Go language is so well designed that the Gno smart contract system will become the new gold standard for smart contract development and other blockchain applications. As a programming language that is universally adopted, secure, composable, and complete, Gno is essential for the broader adoption of web3 and its sustainable growth.` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_gor.gno b/examples/gno.land/r/gnoland/pages/page_gor.gno new file mode 100644 index 00000000000..3a6bb022e09 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_gor.gno @@ -0,0 +1,221 @@ +package gnopages + +func init() { + path := "gor" + title := "Game of Realms Content For The Best Contributors" + // XXX: description := "Game of Realms is the first high-stakes competition held in two phases to find the best contributors to the Gno.land platform with a 133,700 ATOM prize pool." + body := `# Game of Realms + +
+ +### Game of Realms + +The first high-stakes contest will see participants compete for tiered membership to co-own the Gno.land blockchain. A series of complex technical and non-technical tasks will challenge contributors to create innovative patterns that push the chain to new limits. Start building the foundation for tomorrow through key smart contracts and other contributions that change our understanding of the world. + +
+ +The competition is currently in phase one – for advanced developers only. + +Once the necessary tools to start phase two are ready, we’ll open up the competition to newer devs and non-technical contributors. + +If you want to stack ATOM rewards and play a key role in the success of Gno.land and web3, read more about Game of Realms or open a [PR](https://github.com/gnolang/gno/) today. + +
+ +
+
+
+ +## Phase I. (ongoing) + +- + +- + +- + +
+
+ +## Phase II. (Locked) + +
+
+
+ +
+ +
+ +## Evaluation DAO + +This complex challenge seeks your skills in DAO development and implementation and is one of the most important challenges of phase one. The Evaluation DAO will ensure that contributions in Game of Realms and the Gno.land platform are fairly rewarded. + +
+ + + + + + + +
+ +Game of Realms participants and core contributors are still in discussions, proposing additional ideas, and seeing how the proposal for the Evaluation DAO evolves over time. + +
+ + + +
+ +See [GitHub issue 519](https://github.com/gnolang/gno/issues/519) for the most up-to-date discussion so far on how voting should work for the DAO, what the responsibilities are, how to join, etc. + +
+ + + + + + + + + + + + + + + + + +
+
+ +
+ +## Tutorials + +To progress to phase two of the competition, we need high-quality tutorials, guides, and documentation from phase one participants. Help to create materials that will onboard more contributors to Gno.land. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +## Governance Module + +Can you define and implement a governance contract suite that rivals existing ones, such as the Cosmos Hub? Show us how! We’re looking for the fairest and most efficient governance solution possible. + +
+ + + + + + + +
+ +Game of Realms participants and core contributors have made significant progress teaming up to complete this challenge but discussions and additional ideas are still ongoing. + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +## Register Now + + +
+
+ +
+
+ + +
+ +
+ + +
+ + +
+
+
+ +` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_partners.gno b/examples/gno.land/r/gnoland/pages/page_partners.gno new file mode 100644 index 00000000000..440302437fa --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_partners.gno @@ -0,0 +1,21 @@ +package gnopages + +func init() { + path := "partners" + title := "Partners" + // XXX: description := """ + body := `## Partnerships + +### Fund and Grants Program + +Are you a builder, tinkerer, or researcher? If you’re looking to create awesome dApps, tooling, infrastructure, or smart contract libraries on Gno.land, you can apply for a grant. The Gno.land Ecosystem Fund and Grants program provides financial contributions for individuals and teams to innovate on the platform. + +
+ +[More information here](https://github.com/gnolang/ecosystem-fund-grants) + +
+` + + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_start.gno b/examples/gno.land/r/gnoland/pages/page_start.gno new file mode 100644 index 00000000000..a36ec6e52b1 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_start.gno @@ -0,0 +1,21 @@ +package gnopages + +func init() { + path := "start" + title := "Getting Started with Gno" + // XXX: description := "" + + // TODO: codegen to use README files here + + /* TODO: port previous message: This is a demo of Gno smart contract programming. This document was + constructed by Gno onto a smart contract hosted on the data Realm + name ["gno.land/r/demo/boards"](https://gno.land/r/demo/boards/) + ([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)). + */ + body := `## Getting Started with Gno + +- [Install Gno Key](/r/demo/boards:testboard/5) +- TODO: add more links +` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_testnets.gno b/examples/gno.land/r/gnoland/pages/page_testnets.gno new file mode 100644 index 00000000000..b6c09ab71ee --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_testnets.gno @@ -0,0 +1,19 @@ +package gnopages + +func init() { + path := "testnets" + title := "Gno.land Testnets" + // XXX: description := """ + body := `## Other testnets + +- **[staging.gno.land](https://staging.gno.land) (wiped every commit to master)** +- _[test3.gno.land](https://test3.gno.land) (latest)_ +- _[test2.gno.land](https://test2.gno.land) (archive)_ +- _[test1.gno.land](https://test1.gno.land) (archive)_ + +## Local devnet + +See CONTRIBUTING.md on GitHub. +` + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/page_tokenomics.gno b/examples/gno.land/r/gnoland/pages/page_tokenomics.gno new file mode 100644 index 00000000000..de899ae0a70 --- /dev/null +++ b/examples/gno.land/r/gnoland/pages/page_tokenomics.gno @@ -0,0 +1,11 @@ +package gnopages + +func init() { + var ( + path = "tokenomics" + title = "Gno.land Tokenomics" + // XXX: description = """ + body = `Lorem Ipsum` + ) + _ = b.NewPost("", path, title, body, nil) +} diff --git a/examples/gno.land/r/gnoland/pages/pages.gno b/examples/gno.land/r/gnoland/pages/pages.gno index dbc3d855880..6e1f117d1d5 100644 --- a/examples/gno.land/r/gnoland/pages/pages.gno +++ b/examples/gno.land/r/gnoland/pages/pages.gno @@ -4,16 +4,12 @@ import ( "gno.land/p/demo/blog" ) -var b = &blog.Blog{ - Title: "Gnoland's Pages", - Prefix: "/r/gnoland/pages:", -} +// TODO: switch from p/blog to p/pages -func init() { - _ = b.NewPost("", "gor", "Game of Realms", "Lorem Ipsum", nil) - _ = b.NewPost("", "events", "Events", "Lorem Ipsum", nil) - _ = b.NewPost("", "tokenomics", "Tokenomics", "Lorem Ipsum", nil) - _ = b.NewPost("", "start", "Getting Started", "Lorem Ipsum", nil) +var b = &blog.Blog{ + Title: "Gnoland's Pages", + Prefix: "/r/gnoland/pages:", + NoBreadcrumb: true, } func Render(path string) string { diff --git a/examples/gno.land/r/gnoland/pages/pages_test.gno b/examples/gno.land/r/gnoland/pages/pages_test.gno index 1a43153e2c8..5a6fe84ad38 100644 --- a/examples/gno.land/r/gnoland/pages/pages_test.gno +++ b/examples/gno.land/r/gnoland/pages/pages_test.gno @@ -6,48 +6,42 @@ import ( "testing" ) -func TestPackage(t *testing.T) { - std.TestSetOrigCaller(std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq")) - - author := std.GetOrigCaller() - - // by default, lorem ipsum posts - { - got := Render("") - expected := ` -# Gnoland's Pages - -
- -## [Events](/r/gnoland/pages:p/events) -**[Learn More](/r/gnoland/pages:p/events)** - -
- -## [Game of Realms](/r/gnoland/pages:p/gor) -**[Learn More](/r/gnoland/pages:p/gor)** - -
- -## [Getting Started](/r/gnoland/pages:p/start) -**[Learn More](/r/gnoland/pages:p/start)** - -
- -## [Tokenomics](/r/gnoland/pages:p/tokenomics) -**[Learn More](/r/gnoland/pages:p/tokenomics)** - -
-` - assertMDEquals(t, got, expected) +func TestHome(t *testing.T) { + printedOnce := false + got := Render("") + expectedSubtrings := []string{ + "/r/gnoland/pages:p/events", + "/r/gnoland/pages:p/tokenomics", + "/r/gnoland/pages:p/start", + "/r/gnoland/pages:p/gor", + "/r/gnoland/pages:p/about", + "/r/gnoland/pages:p/gnolang", + } + for _, substring := range expectedSubtrings { + if !strings.Contains(got, substring) { + if !printedOnce { + println(got) + printedOnce = true + } + t.Errorf("expected %q, but not found.", substring) + } } } -func assertMDEquals(t *testing.T, got, expected string) { - t.Helper() - expected = strings.TrimSpace(expected) - got = strings.TrimSpace(got) - if expected != got { - t.Errorf("invalid render output.\nexpected %q.\ngot %q.", expected, got) +func TestAbout(t *testing.T) { + printedOnce := false + got := Render("p/about") + expectedSubtrings := []string{ + "# About Gno.land", + "Gno.land is a platform to write smart contracts in Gnolang (Gno).", + } + for _, substring := range expectedSubtrings { + if !strings.Contains(got, substring) { + if !printedOnce { + println(got) + printedOnce = true + } + t.Errorf("expected %q, but not found.", substring) + } } } diff --git a/examples/gno.land/r/manfred/present/admin.gno b/examples/gno.land/r/manfred/present/admin.gno new file mode 100644 index 00000000000..ff0cb075656 --- /dev/null +++ b/examples/gno.land/r/manfred/present/admin.gno @@ -0,0 +1,92 @@ +package present + +import ( + "std" + "strings" + + "gno.land/p/demo/avl" +) + +var ( + adminAddr std.Address + moderatorList avl.Tree + inPause bool +) + +func init() { + // adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis. + adminAddr = "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" +} + +func AdminSetAdminAddr(addr std.Address) { + assertIsAdmin() + adminAddr = addr +} + +func AdminSetInPause(state bool) { + assertIsAdmin() + inPause = state +} + +func AdminAddModerator(addr std.Address) { + assertIsAdmin() + moderatorList.Set(addr.String(), true) +} + +func AdminRemoveModerator(addr std.Address) { + assertIsAdmin() + moderatorList.Set(addr.String(), false) // XXX: delete instead? +} + +func ModAddPost(slug, title, body, tags string) { + assertIsModerator() + + caller := std.GetOrigCaller() + tagList := strings.Split(tags, ",") + err := b.NewPost(caller, slug, title, body, tagList) + checkErr(err) +} + +func ModEditPost(slug, title, body, tags string) { + assertIsModerator() + + tagList := strings.Split(tags, ",") + err := b.GetPost(slug).Update(title, body, tagList) + checkErr(err) +} + +func isAdmin(addr std.Address) bool { + return addr == adminAddr +} + +func isModerator(addr std.Address) bool { + _, found := moderatorList.Get(addr.String()) + return found +} + +func assertIsAdmin() { + caller := std.GetOrigCaller() + if !isAdmin(caller) { + panic("access restricted.") + } +} + +func assertIsModerator() { + caller := std.GetOrigCaller() + if isAdmin(caller) || isModerator(caller) { + return + } + panic("access restricted") +} + +func assertNotInPause() { + if inPause { + panic("access restricted (pause)") + } +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/examples/gno.land/r/manfred/present/gno.mod b/examples/gno.land/r/manfred/present/gno.mod new file mode 100644 index 00000000000..5d50447e0e0 --- /dev/null +++ b/examples/gno.land/r/manfred/present/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/manfred/present + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/blog v0.0.0-latest +) diff --git a/examples/gno.land/r/manfred/present/present_miami23.gno b/examples/gno.land/r/manfred/present/present_miami23.gno new file mode 100644 index 00000000000..36b1980bb0b --- /dev/null +++ b/examples/gno.land/r/manfred/present/present_miami23.gno @@ -0,0 +1,44 @@ +package present + +func init() { + path := "miami23" + title := "Portal Loop Demo (Miami 2023)" + body := ` +# Portal Loop Demo (Miami 2023) + +Rendered by Gno. + +[Source (WIP)](https://github.com/gnolang/gno/pull/1176) + +## Portal Loop + +- DONE: Dynamic homepage, key pages, aliases, and redirects. +- TODO: Deploy with history, complete worxdao v0. +- Will replace the static gno.land site. +- Enhances local development. + +[GitHub Issue](https://github.com/gnolang/gno/issues/1108) + +## Roadmap + +- Crafting the roadmap this week, open to collaboration. +- Combining onchain (portal loop) and offchain (GitHub). +- Next week: Unveiling the official v0 roadmap. + +## Teams, DAOs, Projects + +- Developing worxDAO contracts for directories of projects and teams. +- GitHub teams and projects align with this structure. +- CODEOWNER file updates coming. +- Initial teams announced next week. + +## Tech Team Retreat Plan + +- Continue Portal Loop. +- Consider dApp development. +- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/). +- Engage in workshops. +- Connect and have fun with colleagues. +` + _ = b.NewPost(adminAddr, path, title, body, []string{"demo", "portal-loop", "miami"}) +} diff --git a/examples/gno.land/r/manfred/present/present_miami23_filetest.gno b/examples/gno.land/r/manfred/present/present_miami23_filetest.gno new file mode 100644 index 00000000000..05c41905060 --- /dev/null +++ b/examples/gno.land/r/manfred/present/present_miami23_filetest.gno @@ -0,0 +1,57 @@ +package main + +import "gno.land/r/manfred/present" + +func main() { + println(present.Render("")) + println("------------------------------------") + println(present.Render("p/miami23")) +} + +// Output: +//
+// +// ## [Portal Loop Demo (Miami 2023)](/r/manfred/present:p/miami23) +// **[Learn More](/r/manfred/present:p/miami23)** +// +//
+// ------------------------------------ +// # Portal Loop Demo (Miami 2023) +// +// Rendered by Gno. +// +// [Source (WIP)](https://github.com/gnolang/gno/pull/1176) +// +// ## Portal Loop +// +// - DONE: Dynamic homepage, key pages, aliases, and redirects. +// - TODO: Deploy with history, complete worxdao v0. +// - Will replace the static gno.land site. +// - Enhances local development. +// +// [GitHub Issue](https://github.com/gnolang/gno/issues/1108) +// +// ## Roadmap +// +// - Crafting the roadmap this week, open to collaboration. +// - Combining onchain (portal loop) and offchain (GitHub). +// - Next week: Unveiling the official v0 roadmap. +// +// ## Teams, DAOs, Projects +// +// - Developing worxDAO contracts for directories of projects and teams. +// - GitHub teams and projects align with this structure. +// - CODEOWNER file updates coming. +// - Initial teams announced next week. +// +// ## Tech Team Retreat Plan +// +// - Continue Portal Loop. +// - Consider dApp development. +// - Explore new topics [here](https://github.com/orgs/gnolang/projects/15/). +// - Engage in workshops. +// - Connect and have fun with colleagues. +// +// [#demo](/r/manfred/present:t/demo) [#portal-loop](/r/manfred/present:t/portal-loop) [#miami](/r/manfred/present:t/miami) +// +// by g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq on 1970-01-01 12:00am UTC diff --git a/examples/gno.land/r/manfred/present/presentations.gno b/examples/gno.land/r/manfred/present/presentations.gno new file mode 100644 index 00000000000..8a99f502e86 --- /dev/null +++ b/examples/gno.land/r/manfred/present/presentations.gno @@ -0,0 +1,17 @@ +package present + +import ( + "gno.land/p/demo/blog" +) + +// TODO: switch from p/blog to p/present + +var b = &blog.Blog{ + Title: "Manfred's Presentations", + Prefix: "/r/manfred/present:", + NoBreadcrumb: true, +} + +func Render(path string) string { + return b.Render(path) +} diff --git a/examples/gno.land/r/system/names/gno.mod b/examples/gno.land/r/system/names/gno.mod index 31c456f90e0..cd4fd0aae4a 100644 --- a/examples/gno.land/r/system/names/gno.mod +++ b/examples/gno.land/r/system/names/gno.mod @@ -1,5 +1,3 @@ module gno.land/r/system/names -require ( - "gno.land/p/demo/avl" v0.0.0-latest -) +require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/r/x/manfred_outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod index eef8ec5956e..e6f705c46b9 100644 --- a/examples/gno.land/r/x/manfred_outfmt/gno.mod +++ b/examples/gno.land/r/x/manfred_outfmt/gno.mod @@ -1,6 +1,6 @@ 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 + 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/nir1218_evaluation_proposal/EVALUATION.md b/examples/gno.land/r/x/nir1218_evaluation_proposal/EVALUATION.md index b1dc2de58df..4926713e8fa 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/EVALUATION.md +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/EVALUATION.md @@ -51,7 +51,7 @@ An example of a category is a bounty, a chore, a defect, or a document. A contribution is associated with a pull request. A contribution has an evaluation life cycle. A submission time is set when a contribution is added. -A last evaluation time is set when a contribution is evaluated and approved by a memeber. +A last evaluation time is set when a contribution is evaluated and approved by a member. An approval time is set when a contribution is approved by all members (or when a future threshold is reached) #### Submission diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno index ed939b56fdb..1ec801bb971 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno @@ -9,7 +9,7 @@ import ( type Committee struct { members []std.Address // TODO - use avl tree or address set? - categories avl.Tree // A catagory is mapped to a list of evaluation criteria + categories avl.Tree // A category is mapped to a list of evaluation criteria evaluation *Evaluation } @@ -84,7 +84,7 @@ func (c *Committee) AddContribution(pr *PullRequest, contributor std.Address) (c if !c.isMember(std.GetOrigCaller()) { return -1, false } - // Check the category of the PR matches a catagory this committee evaluates + // Check the category of the PR matches a category this committee evaluates // TODO check the category is an approved category if c.categories.Has(pr.category) { return c.evaluation.AddContribution(pr, contributor) diff --git a/gno.land/Makefile b/gno.land/Makefile index e794bb58174..29c192e9987 100644 --- a/gno.land/Makefile +++ b/gno.land/Makefile @@ -5,23 +5,28 @@ help: rundep=go run -modfile ../misc/devdeps/go.mod +.PHONY: gnoland.start +gnoland.start:; go run ./cmd/gnoland start + .PHONY: build -build: build.gnoland build.gnokey build.gnoweb build.gnofaucet build.gnotxsync +build: build.gnoland build.gnokey build.gnoweb build.gnofaucet build.gnotxsync build.genesis build.gnoland:; go build -o build/gnoland ./cmd/gnoland build.gnoweb:; go build -o build/gnoweb ./cmd/gnoweb build.gnofaucet:; go build -o build/gnofaucet ./cmd/gnofaucet build.gnokey:; go build -o build/gnokey ./cmd/gnokey build.gnotxsync:; go build -o build/gnotxsync ./cmd/gnotxsync +build.genesis:; go build -o build/genesis ./cmd/genesis .PHONY: install -install: install.gnoland install.gnoweb install.gnofaucet install.gnokey install.gnotxsync +install: install.gnoland install.gnoweb install.gnofaucet install.gnokey install.gnotxsync install.genesis install.gnoland:; go install ./cmd/gnoland install.gnoweb:; go install ./cmd/gnoweb install.gnofaucet:; go install ./cmd/gnofaucet install.gnokey:; go install ./cmd/gnokey install.gnotxsync:; go install ./cmd/gnotxsync +install.genesis:; go install ./cmd/genesis .PHONY: fclean fclean: clean diff --git a/gno.land/cmd/genesis/balances.go b/gno.land/cmd/genesis/balances.go new file mode 100644 index 00000000000..0e81f280f33 --- /dev/null +++ b/gno.land/cmd/genesis/balances.go @@ -0,0 +1,39 @@ +package main + +import ( + "flag" + + "github.com/gnolang/gno/tm2/pkg/commands" +) + +type balancesCfg struct { + commonCfg +} + +// newBalancesCmd creates the genesis balances subcommand +func newBalancesCmd(io commands.IO) *commands.Command { + cfg := &balancesCfg{} + + cmd := commands.NewCommand( + commands.Metadata{ + Name: "balances", + ShortUsage: "balances [flags]", + LongHelp: "Manipulates the initial genesis.json account balances (pre-mines)", + ShortHelp: "Manages genesis.json account balances", + }, + cfg, + commands.HelpExec, + ) + + cmd.AddSubCommands( + newBalancesAddCmd(cfg, io), + newBalancesRemoveCmd(cfg, io), + newBalancesExportCmd(cfg, io), + ) + + return cmd +} + +func (c *balancesCfg) RegisterFlags(fs *flag.FlagSet) { + c.commonCfg.RegisterFlags(fs) +} diff --git a/gno.land/cmd/genesis/balances_add.go b/gno.land/cmd/genesis/balances_add.go new file mode 100644 index 00000000000..d1e88efcc6b --- /dev/null +++ b/gno.land/cmd/genesis/balances_add.go @@ -0,0 +1,348 @@ +package main + +import ( + "bufio" + "context" + "errors" + "flag" + "fmt" + "io" + "os" + "strings" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/std" + + _ "github.com/gnolang/gno/gno.land/pkg/sdk/vm" +) + +var ( + errNoBalanceSource = errors.New("at least one balance source must be set") + errBalanceParsingAborted = errors.New("balance parsing aborted") + errInvalidAddress = errors.New("invalid address encountered") +) + +type balancesAddCfg struct { + rootCfg *balancesCfg + + balanceSheet string + singleEntries commands.StringArr + parseExport string +} + +// newBalancesAddCmd creates the genesis balances add subcommand +func newBalancesAddCmd(rootCfg *balancesCfg, io commands.IO) *commands.Command { + cfg := &balancesAddCfg{ + rootCfg: rootCfg, + } + + return commands.NewCommand( + commands.Metadata{ + Name: "add", + ShortUsage: "balances add [flags]", + LongHelp: "Adds a new validator to the genesis.json", + }, + cfg, + func(ctx context.Context, _ []string) error { + return execBalancesAdd(ctx, cfg, io) + }, + ) +} + +func (c *balancesAddCfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.balanceSheet, + "balance-sheet", + "", + "the path to the balance file containing addresses in the format
=ugnot", + ) + + fs.Var( + &c.singleEntries, + "single", + "the direct balance addition in the format
=ugnot", + ) + + fs.StringVar( + &c.parseExport, + "parse-export", + "", + "the path to the transaction export containing a list of transactions (JSONL)", + ) +} + +func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io commands.IO) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.rootCfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + // Validate the source is set correctly + var ( + singleEntriesSet = len(cfg.singleEntries) != 0 + balanceSheetSet = cfg.balanceSheet != "" + txFileSet = cfg.parseExport != "" + ) + + if !singleEntriesSet && !balanceSheetSet && !txFileSet { + return errNoBalanceSource + } + + finalBalances := make(accountBalances) + + // Get the balance sheet from the source + if singleEntriesSet { + balances, err := getBalancesFromEntries(cfg.singleEntries) + if err != nil { + return fmt.Errorf("unable to get balances from entries, %w", err) + } + + finalBalances.leftMerge(balances) + } + + if balanceSheetSet { + // Open the balance sheet + file, loadErr := os.Open(cfg.balanceSheet) + if loadErr != nil { + return fmt.Errorf("unable to open balance sheet, %w", loadErr) + } + + balances, err := getBalancesFromSheet(file) + if err != nil { + return fmt.Errorf("unable to get balances from balance sheet, %w", err) + } + + finalBalances.leftMerge(balances) + } + + if txFileSet { + // Open the transactions file + file, loadErr := os.Open(cfg.parseExport) + if loadErr != nil { + return fmt.Errorf("unable to open transactions file, %w", loadErr) + } + + balances, err := getBalancesFromTransactions(ctx, io, file) + if err != nil { + return fmt.Errorf("unable to get balances from tx file, %w", err) + } + + finalBalances.leftMerge(balances) + } + + // Initialize genesis app state if it is not initialized already + if genesis.AppState == nil { + genesis.AppState = gnoland.GnoGenesisState{} + } + + // Construct the initial genesis balance sheet + state := genesis.AppState.(gnoland.GnoGenesisState) + genesisBalances, err := mapGenesisBalancesFromState(state) + if err != nil { + return err + } + + // Merge the two balance sheets, with the input + // having precedence over the genesis balances + finalBalances.leftMerge(genesisBalances) + + // Save the balances + state.Balances = finalBalances.toList() + genesis.AppState = state + + // Save the updated genesis + if err := genesis.SaveAs(cfg.rootCfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + io.Printfln( + "%d pre-mines saved", + len(finalBalances), + ) + + io.Println() + + for address, balance := range finalBalances { + io.Printfln("%s:%dugnot", address.String(), balance) + } + + return nil +} + +// getBalancesFromEntries extracts the balance entries +// from the array of balance +func getBalancesFromEntries(entries []string) (accountBalances, error) { + balances := make(accountBalances) + + for _, entry := range entries { + var balance gnoland.Balance + if err := balance.Parse(entry); err != nil { + return nil, fmt.Errorf("unable to parse balance entry: %w", err) + } + balances[balance.Address] = balance + } + + return balances, nil +} + +// getBalancesFromSheet extracts the balance sheet from the passed in +// balance sheet file, that has the format of
=ugnot +func getBalancesFromSheet(sheet io.Reader) (accountBalances, error) { + // Parse the balances + balances := make(accountBalances) + scanner := bufio.NewScanner(sheet) + + for scanner.Scan() { + entry := scanner.Text() + + // Remove comments + entry = strings.Split(entry, "#")[0] + entry = strings.TrimSpace(entry) + + // Skip empty lines + if entry == "" { + continue + } + + var balance gnoland.Balance + if err := balance.Parse(entry); err != nil { + return nil, fmt.Errorf("unable to extract balance data, %w", err) + } + + balances[balance.Address] = balance + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error encountered while scanning, %w", err) + } + + return balances, nil +} + +// getBalancesFromTransactions constructs a balance map based on MsgSend messages. +// This way of determining the final balance sheet is not valid, since it doesn't take into +// account different message types (ex. MsgCall) that can initialize accounts with some balance values. +// The right way to do this sort of initialization is to spin up an in-memory node +// and execute the entire transaction history to determine touched accounts and final balances, +// and construct a balance sheet based off of this information +func getBalancesFromTransactions( + ctx context.Context, + io commands.IO, + reader io.Reader, +) (accountBalances, error) { + balances := make(accountBalances) + + scanner := bufio.NewScanner(reader) + + for scanner.Scan() { + select { + case <-ctx.Done(): + return nil, errBalanceParsingAborted + default: + // Parse the amino JSON + var tx std.Tx + + line := scanner.Bytes() + + if err := amino.UnmarshalJSON(line, &tx); err != nil { + io.ErrPrintfln( + "invalid amino JSON encountered: %q", + string(line), + ) + + continue + } + + feeAmount := std.NewCoins(tx.Fee.GasFee) + if feeAmount.AmountOf("ugnot") <= 0 { + io.ErrPrintfln( + "invalid gas fee amount encountered: %q", + tx.Fee.GasFee.String(), + ) + } + + for _, msg := range tx.Msgs { + if msg.Type() != "send" { + continue + } + + msgSend := msg.(bank.MsgSend) + + sendAmount := msgSend.Amount + if sendAmount.AmountOf("ugnot") <= 0 { + io.ErrPrintfln( + "invalid send amount encountered: %s", + msgSend.Amount.String(), + ) + continue + } + + // This way of determining final account balances is not really valid, + // because we take into account only the ugnot transfer messages (MsgSend) + // and not other message types (like MsgCall), that can also + // initialize accounts with some balances. Because of this, + // we can run into a situation where a message send amount or fee + // causes an accounts balance to go < 0. In these cases, + // we initialize the account (it is present in the balance sheet), but + // with the balance of 0 + + from := balances[msgSend.FromAddress].Amount + to := balances[msgSend.ToAddress].Amount + + to = to.Add(sendAmount) + + if from.IsAllLT(sendAmount) || from.IsAllLT(feeAmount) { + // Account cannot cover send amount / fee + // (see message above) + from = std.NewCoins(std.NewCoin("ugnot", 0)) + } + + if from.IsAllGT(sendAmount) { + from = from.Sub(sendAmount) + } + + if from.IsAllGT(feeAmount) { + from = from.Sub(feeAmount) + } + + // Set new balance + balances[msgSend.FromAddress] = gnoland.Balance{ + Address: msgSend.FromAddress, + Amount: from, + } + balances[msgSend.ToAddress] = gnoland.Balance{ + Address: msgSend.ToAddress, + Amount: to, + } + } + } + } + + // Check for scanning errors + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf( + "error encountered while reading file, %w", + err, + ) + } + + return balances, nil +} + +// mapGenesisBalancesFromState extracts the initial account balances from the +// genesis app state +func mapGenesisBalancesFromState(state gnoland.GnoGenesisState) (accountBalances, error) { + // Construct the initial genesis balance sheet + genesisBalances := make(accountBalances) + + for _, balance := range state.Balances { + genesisBalances[balance.Address] = balance + } + + return genesisBalances, nil +} diff --git a/gno.land/cmd/genesis/balances_add_test.go b/gno.land/cmd/genesis/balances_add_test.go new file mode 100644 index 00000000000..73e2fe148a2 --- /dev/null +++ b/gno.land/cmd/genesis/balances_add_test.go @@ -0,0 +1,704 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "math" + "strconv" + "strings" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenesis_Balances_Add(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis", func(t *testing.T) { + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "add", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("no sources selected", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "add", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errNoBalanceSource.Error()) + }) + + t.Run("invalid genesis path", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "add", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("balances from entries", func(t *testing.T) { + t.Parallel() + + dummyKeys := getDummyKeys(t, 2) + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "add", + "--genesis-path", + tempGenesis.Name(), + } + + amount := std.NewCoins(std.NewCoin("ugnot", 10)) + + for _, dummyKey := range dummyKeys { + args = append(args, "--single") + args = append( + args, + fmt.Sprintf( + "%s=%dugnot", + dummyKey.Address().String(), + amount.AmountOf("ugnot"), + ), + ) + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the genesis was updated + genesis, loadErr := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, loadErr) + + require.NotNil(t, genesis.AppState) + + state, ok := genesis.AppState.(gnoland.GnoGenesisState) + require.True(t, ok) + + require.Equal(t, len(dummyKeys), len(state.Balances)) + + for _, balance := range state.Balances { + // Find the appropriate key + // (the genesis is saved with randomized balance order) + found := false + for _, dummyKey := range dummyKeys { + if dummyKey.Address().String() == balance.Address.String() { + assert.Equal(t, amount, balance.Amount) + + found = true + break + } + } + + if !found { + t.Fatalf("unexpected entry with address %s found", balance.Address.String()) + } + } + }) + + t.Run("balances from sheet", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + dummyKeys := getDummyKeys(t, 10) + amount := std.NewCoins(std.NewCoin("ugnot", 10)) + + balances := make([]string, len(dummyKeys)) + + // Add a random comment to the balances file output + balances = append(balances, "#comment\n") + + for index, key := range dummyKeys { + balances[index] = fmt.Sprintf( + "%s=%dugnot", + key.Address().String(), + amount.AmountOf("ugnot"), + ) + } + + // Write the balance sheet to a file + balanceSheet, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + _, err := balanceSheet.WriteString(strings.Join(balances, "\n")) + require.NoError(t, err) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "add", + "--genesis-path", + tempGenesis.Name(), + "--balance-sheet", + balanceSheet.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the genesis was updated + genesis, loadErr := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, loadErr) + + require.NotNil(t, genesis.AppState) + + state, ok := genesis.AppState.(gnoland.GnoGenesisState) + require.True(t, ok) + + require.Equal(t, len(dummyKeys), len(state.Balances)) + + for _, balance := range state.Balances { + // Find the appropriate key + // (the genesis is saved with randomized balance order) + found := false + for _, dummyKey := range dummyKeys { + if dummyKey.Address().String() == balance.Address.String() { + assert.Equal(t, amount, balance.Amount) + + found = true + break + } + } + + if !found { + t.Fatalf("unexpected entry with address %s found", balance.Address.String()) + } + } + }) + + t.Run("balances from transactions", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + var ( + dummyKeys = getDummyKeys(t, 10) + amount = std.NewCoins(std.NewCoin("ugnot", 10)) + amountCoins = std.NewCoins(std.NewCoin("ugnot", 10)) + gasFee = std.NewCoin("ugnot", 1000000) + txs = make([]std.Tx, 0) + ) + + sender := dummyKeys[0] + for _, dummyKey := range dummyKeys[1:] { + tx := std.Tx{ + Msgs: []std.Msg{ + bank.MsgSend{ + FromAddress: sender.Address(), + ToAddress: dummyKey.Address(), + Amount: amountCoins, + }, + }, + Fee: std.Fee{ + GasWanted: 10, + GasFee: gasFee, + }, + Signatures: make([]std.Signature, 0), + } + + txs = append(txs, tx) + } + + // Marshal the transactions into amino JSON + marshalledTxs := make([]string, 0, len(txs)) + + for _, tx := range txs { + marshalledTx, err := amino.MarshalJSON(tx) + require.NoError(t, err) + + marshalledTxs = append(marshalledTxs, string(marshalledTx)) + } + + // Write the transactions to a file + txsFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + _, err := txsFile.WriteString(strings.Join(marshalledTxs, "\n")) + require.NoError(t, err) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "add", + "--genesis-path", + tempGenesis.Name(), + "--parse-export", + txsFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the genesis was updated + genesis, loadErr := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, loadErr) + + require.NotNil(t, genesis.AppState) + + state, ok := genesis.AppState.(gnoland.GnoGenesisState) + require.True(t, ok) + + require.Equal(t, len(dummyKeys), len(state.Balances)) + + for _, balance := range state.Balances { + // Find the appropriate key + // (the genesis is saved with randomized balance order) + found := false + for index, dummyKey := range dummyKeys { + checkAmount := amount + if index == 0 { + // the first address should + // have a balance of 0 + checkAmount = std.NewCoins(std.NewCoin("ugnot", 0)) + } + + if dummyKey.Address().String() == balance.Address.String() { + assert.True(t, balance.Amount.IsEqual(checkAmount)) + + found = true + break + } + } + + if !found { + t.Fatalf("unexpected entry with address %s found", balance.Address.String()) + } + } + }) + + t.Run("balances overwrite", func(t *testing.T) { + t.Parallel() + + dummyKeys := getDummyKeys(t, 10) + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + state := gnoland.GnoGenesisState{ + // Set an initial balance value + Balances: []gnoland.Balance{ + { + Address: dummyKeys[0].Address(), + Amount: std.NewCoins(std.NewCoin("ugnot", 100)), + }, + }, + } + genesis.AppState = state + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "add", + "--genesis-path", + tempGenesis.Name(), + } + + amount := std.NewCoins(std.NewCoin("ugnot", 10)) + + for _, dummyKey := range dummyKeys { + args = append(args, "--single") + args = append( + args, + fmt.Sprintf( + "%s=%dugnot", + dummyKey.Address().String(), + amount.AmountOf("ugnot"), + ), + ) + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the genesis was updated + genesis, loadErr := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, loadErr) + + require.NotNil(t, genesis.AppState) + + state, ok := genesis.AppState.(gnoland.GnoGenesisState) + require.True(t, ok) + + require.Equal(t, len(dummyKeys), len(state.Balances)) + + for _, balance := range state.Balances { + // Find the appropriate key + // (the genesis is saved with randomized balance order) + found := false + for _, dummyKey := range dummyKeys { + if dummyKey.Address().String() == balance.Address.String() { + assert.Equal(t, amount, balance.Amount) + + found = true + break + } + } + + if !found { + t.Fatalf("unexpected entry with address %s found", balance.Address.String()) + } + } + }) +} + +func TestBalances_GetBalancesFromEntries(t *testing.T) { + t.Parallel() + + t.Run("valid balances", func(t *testing.T) { + t.Parallel() + + // Generate dummy keys + dummyKeys := getDummyKeys(t, 2) + amount := std.NewCoins(std.NewCoin("ugnot", 10)) + + balances := make([]string, len(dummyKeys)) + + for index, key := range dummyKeys { + balances[index] = fmt.Sprintf( + "%s=%dugnot", + key.Address().String(), + amount.AmountOf("ugnot"), + ) + } + + balanceMap, err := getBalancesFromEntries(balances) + require.NoError(t, err) + + // Validate the balance map + assert.Len(t, balanceMap, len(dummyKeys)) + for _, key := range dummyKeys { + assert.Equal(t, amount, balanceMap[key.Address()].Amount) + } + }) + + t.Run("malformed balance, invalid format", func(t *testing.T) { + t.Parallel() + + balances := []string{ + "malformed balance", + } + + balanceMap, err := getBalancesFromEntries(balances) + + assert.Nil(t, balanceMap) + assert.ErrorContains(t, err, "malformed entry") + }) + + t.Run("malformed balance, invalid address", func(t *testing.T) { + t.Parallel() + + balances := []string{ + "dummyaddress=10ugnot", + } + + balanceMap, err := getBalancesFromEntries(balances) + + assert.Nil(t, balanceMap) + assert.ErrorContains(t, err, "invalid address") + }) + + t.Run("malformed balance, invalid amount", func(t *testing.T) { + t.Parallel() + + dummyKey := getDummyKey(t) + + balances := []string{ + fmt.Sprintf( + "%s=%sugnot", + dummyKey.Address().String(), + strconv.FormatUint(math.MaxUint64, 10), + ), + } + + balanceMap, err := getBalancesFromEntries(balances) + + assert.Nil(t, balanceMap) + assert.ErrorContains(t, err, "invalid amount") + }) +} + +func TestBalances_GetBalancesFromSheet(t *testing.T) { + t.Parallel() + + t.Run("valid balances", func(t *testing.T) { + t.Parallel() + + // Generate dummy keys + dummyKeys := getDummyKeys(t, 2) + amount := std.NewCoins(std.NewCoin("ugnot", 10)) + + balances := make([]string, len(dummyKeys)) + + for index, key := range dummyKeys { + balances[index] = fmt.Sprintf( + "%s=%dugnot", + key.Address().String(), + amount.AmountOf("ugnot"), + ) + } + + reader := strings.NewReader(strings.Join(balances, "\n")) + balanceMap, err := getBalancesFromSheet(reader) + require.NoError(t, err) + + // Validate the balance map + assert.Len(t, balanceMap, len(dummyKeys)) + for _, key := range dummyKeys { + assert.Equal(t, amount, balanceMap[key.Address()].Amount) + } + }) + + t.Run("malformed balance, invalid amount", func(t *testing.T) { + t.Parallel() + + dummyKey := getDummyKey(t) + + balances := []string{ + fmt.Sprintf( + "%s=%sugnot", + dummyKey.Address().String(), + strconv.FormatUint(math.MaxUint64, 10), + ), + } + + reader := strings.NewReader(strings.Join(balances, "\n")) + + balanceMap, err := getBalancesFromSheet(reader) + + assert.Nil(t, balanceMap) + assert.ErrorContains(t, err, "invalid amount") + }) +} + +func TestBalances_GetBalancesFromTransactions(t *testing.T) { + t.Parallel() + + t.Run("valid transactions", func(t *testing.T) { + t.Parallel() + + var ( + dummyKeys = getDummyKeys(t, 10) + amount = std.NewCoins(std.NewCoin("ugnot", 10)) + amountCoins = std.NewCoins(std.NewCoin("ugnot", 10)) + gasFee = std.NewCoin("ugnot", 1000000) + txs = make([]std.Tx, 0) + ) + + sender := dummyKeys[0] + for _, dummyKey := range dummyKeys[1:] { + tx := std.Tx{ + Msgs: []std.Msg{ + bank.MsgSend{ + FromAddress: sender.Address(), + ToAddress: dummyKey.Address(), + Amount: amountCoins, + }, + }, + Fee: std.Fee{ + GasWanted: 10, + GasFee: gasFee, + }, + Signatures: make([]std.Signature, 0), + } + + txs = append(txs, tx) + } + + // Marshal the transactions into amino JSON + marshalledTxs := make([]string, 0, len(txs)) + + for _, tx := range txs { + marshalledTx, err := amino.MarshalJSON(tx) + require.NoError(t, err) + + marshalledTxs = append(marshalledTxs, string(marshalledTx)) + } + + mockErr := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetErr(commands.WriteNopCloser(mockErr)) + + reader := strings.NewReader(strings.Join(marshalledTxs, "\n")) + balanceMap, err := getBalancesFromTransactions(context.Background(), io, reader) + require.NoError(t, err) + + // Validate the balance map + assert.Len(t, balanceMap, len(dummyKeys)) + for _, key := range dummyKeys[1:] { + assert.Equal(t, amount, balanceMap[key.Address()].Amount) + } + + assert.Equal(t, std.Coins{}, balanceMap[sender.Address()].Amount) + }) + + t.Run("malformed transaction, invalid fee amount", func(t *testing.T) { + t.Parallel() + + var ( + dummyKeys = getDummyKeys(t, 10) + amountCoins = std.NewCoins(std.NewCoin("ugnot", 10)) + gasFee = std.NewCoin("gnos", 1) // invalid fee + txs = make([]std.Tx, 0) + ) + + sender := dummyKeys[0] + for _, dummyKey := range dummyKeys[1:] { + tx := std.Tx{ + Msgs: []std.Msg{ + bank.MsgSend{ + FromAddress: sender.Address(), + ToAddress: dummyKey.Address(), + Amount: amountCoins, + }, + }, + Fee: std.Fee{ + GasWanted: 10, + GasFee: gasFee, + }, + Signatures: make([]std.Signature, 0), + } + + txs = append(txs, tx) + } + + // Marshal the transactions into amino JSON + marshalledTxs := make([]string, 0, len(txs)) + + for _, tx := range txs { + marshalledTx, err := amino.MarshalJSON(tx) + require.NoError(t, err) + + marshalledTxs = append(marshalledTxs, string(marshalledTx)) + } + + mockErr := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetErr(commands.WriteNopCloser(mockErr)) + + reader := strings.NewReader(strings.Join(marshalledTxs, "\n")) + balanceMap, err := getBalancesFromTransactions(context.Background(), io, reader) + require.NoError(t, err) + + assert.NotNil(t, balanceMap) + assert.Contains(t, mockErr.String(), "invalid gas fee amount") + }) + + t.Run("malformed transaction, invalid send amount", func(t *testing.T) { + t.Parallel() + + var ( + dummyKeys = getDummyKeys(t, 10) + amountCoins = std.NewCoins(std.NewCoin("gnogno", 10)) // invalid send amount + gasFee = std.NewCoin("ugnot", 1) + txs = make([]std.Tx, 0) + ) + + sender := dummyKeys[0] + for _, dummyKey := range dummyKeys[1:] { + tx := std.Tx{ + Msgs: []std.Msg{ + bank.MsgSend{ + FromAddress: sender.Address(), + ToAddress: dummyKey.Address(), + Amount: amountCoins, + }, + }, + Fee: std.Fee{ + GasWanted: 10, + GasFee: gasFee, + }, + Signatures: make([]std.Signature, 0), + } + + txs = append(txs, tx) + } + + // Marshal the transactions into amino JSON + marshalledTxs := make([]string, 0, len(txs)) + + for _, tx := range txs { + marshalledTx, err := amino.MarshalJSON(tx) + require.NoError(t, err) + + marshalledTxs = append(marshalledTxs, string(marshalledTx)) + } + + mockErr := bytes.NewBufferString("") + io := commands.NewTestIO() + io.SetErr(commands.WriteNopCloser(mockErr)) + + reader := strings.NewReader(strings.Join(marshalledTxs, "\n")) + balanceMap, err := getBalancesFromTransactions(context.Background(), io, reader) + require.NoError(t, err) + + assert.NotNil(t, balanceMap) + assert.Contains(t, mockErr.String(), "invalid send amount") + }) +} diff --git a/gno.land/cmd/genesis/balances_export.go b/gno.land/cmd/genesis/balances_export.go new file mode 100644 index 00000000000..c07f250afeb --- /dev/null +++ b/gno.land/cmd/genesis/balances_export.go @@ -0,0 +1,78 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +// newBalancesExportCmd creates the genesis balances export subcommand +func newBalancesExportCmd(balancesCfg *balancesCfg, io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "export", + ShortUsage: "balances export [flags] ", + ShortHelp: "Exports the balances from the genesis.json", + LongHelp: "Exports the balances from the genesis.json to an output file", + }, + commands.NewEmptyConfig(), + func(_ context.Context, args []string) error { + return execBalancesExport(balancesCfg, io, args) + }, + ) +} + +func execBalancesExport(cfg *balancesCfg, io commands.IO, args []string) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + // Load the genesis state + if genesis.AppState == nil { + return errAppStateNotSet + } + + state := genesis.AppState.(gnoland.GnoGenesisState) + if len(state.Balances) == 0 { + io.Println("No genesis balances to export") + + return nil + } + + // Make sure the output file path is specified + if len(args) == 0 { + return errNoOutputFile + } + + // Open output file + outputFile, err := os.OpenFile( + args[0], + os.O_RDWR|os.O_CREATE|os.O_APPEND, + 0o755, + ) + if err != nil { + return fmt.Errorf("unable to create output file, %w", err) + } + + // Save the balances + for _, balance := range state.Balances { + if _, err = outputFile.WriteString( + fmt.Sprintf("%s\n", balance), + ); err != nil { + return fmt.Errorf("unable to write to output, %w", err) + } + } + + io.Printfln( + "Exported %d balances", + len(state.Balances), + ) + + return nil +} diff --git a/gno.land/cmd/genesis/balances_export_test.go b/gno.land/cmd/genesis/balances_export_test.go new file mode 100644 index 00000000000..d7441fd438f --- /dev/null +++ b/gno.land/cmd/genesis/balances_export_test.go @@ -0,0 +1,158 @@ +package main + +import ( + "bufio" + "context" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// getDummyBalances generates dummy balance lines +func getDummyBalances(t *testing.T, count int) []gnoland.Balance { + t.Helper() + + dummyKeys := getDummyKeys(t, count) + amount := std.NewCoins(std.NewCoin("ugnot", 10)) + + balances := make([]gnoland.Balance, len(dummyKeys)) + + for index, key := range dummyKeys { + balances[index] = gnoland.Balance{ + Address: key.Address(), + Amount: amount, + } + } + + return balances +} + +func TestGenesis_Balances_Export(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis file", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "export", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("invalid genesis app state", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + genesis.AppState = nil // no app state + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "export", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errAppStateNotSet.Error()) + }) + + t.Run("no output file specified", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + genesis.AppState = gnoland.GnoGenesisState{ + Balances: getDummyBalances(t, 1), + } + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "export", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errNoOutputFile.Error()) + }) + + t.Run("valid balances export", func(t *testing.T) { + t.Parallel() + + // Generate dummy balances + balances := getDummyBalances(t, 10) + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + genesis.AppState = gnoland.GnoGenesisState{ + Balances: balances, + } + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Prepare the output file + outputFile, outputCleanup := testutils.NewTestFile(t) + t.Cleanup(outputCleanup) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "export", + "--genesis-path", + tempGenesis.Name(), + outputFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the transactions were written down + scanner := bufio.NewScanner(outputFile) + + outputBalances := make([]gnoland.Balance, 0) + for scanner.Scan() { + var balance gnoland.Balance + err := balance.Parse(scanner.Text()) + require.NoError(t, err) + + outputBalances = append(outputBalances, balance) + } + + require.NoError(t, scanner.Err()) + + assert.Len(t, outputBalances, len(balances)) + + for index, balance := range outputBalances { + assert.Equal(t, balances[index], balance) + } + }) +} diff --git a/gno.land/cmd/genesis/balances_remove.go b/gno.land/cmd/genesis/balances_remove.go new file mode 100644 index 00000000000..a752bbda4fd --- /dev/null +++ b/gno.land/cmd/genesis/balances_remove.go @@ -0,0 +1,103 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" +) + +var ( + errUnableToLoadGenesis = errors.New("unable to load genesis") + errBalanceNotFound = errors.New("genesis balances entry does not exist") +) + +type balancesRemoveCfg struct { + rootCfg *balancesCfg + + address string +} + +// newBalancesRemoveCmd creates the genesis balances remove subcommand +func newBalancesRemoveCmd(rootCfg *balancesCfg, io commands.IO) *commands.Command { + cfg := &balancesRemoveCfg{ + rootCfg: rootCfg, + } + + return commands.NewCommand( + commands.Metadata{ + Name: "remove", + ShortUsage: "balances remove [flags]", + LongHelp: "Removes the balance information of a specific account", + }, + cfg, + func(_ context.Context, _ []string) error { + return execBalancesRemove(cfg, io) + }, + ) +} + +func (c *balancesRemoveCfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.address, + "address", + "", + "the address of the account whose balance information should be removed from genesis.json", + ) +} + +func execBalancesRemove(cfg *balancesRemoveCfg, io commands.IO) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.rootCfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("%w, %w", errUnableToLoadGenesis, loadErr) + } + + // Validate the address + address, err := crypto.AddressFromString(cfg.address) + if err != nil { + return fmt.Errorf("%w, %w", errInvalidAddress, err) + } + + // Check if the genesis state is set at all + if genesis.AppState == nil { + return errAppStateNotSet + } + + // Construct the initial genesis balance sheet + state := genesis.AppState.(gnoland.GnoGenesisState) + genesisBalances, err := mapGenesisBalancesFromState(state) + if err != nil { + return err + } + + // Check if the genesis balance for the account is present + _, exists := genesisBalances[address] + if !exists { + return errBalanceNotFound + } + + // Drop the account pre-mine + delete(genesisBalances, address) + + // Save the balances + state.Balances = genesisBalances.toList() + genesis.AppState = state + + // Save the updated genesis + if err := genesis.SaveAs(cfg.rootCfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + io.Printfln( + "Pre-mine information for address %s removed", + address.String(), + ) + + return nil +} diff --git a/gno.land/cmd/genesis/balances_remove_test.go b/gno.land/cmd/genesis/balances_remove_test.go new file mode 100644 index 00000000000..b9d10d0db08 --- /dev/null +++ b/gno.land/cmd/genesis/balances_remove_test.go @@ -0,0 +1,140 @@ +package main + +import ( + "context" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenesis_Balances_Remove(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis", func(t *testing.T) { + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "remove", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("genesis app state not set", func(t *testing.T) { + t.Parallel() + + dummyKey := getDummyKey(t) + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + genesis.AppState = nil // not set + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "remove", + "--genesis-path", + tempGenesis.Name(), + "--address", + dummyKey.Address().String(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.ErrorContains(t, cmdErr, errAppStateNotSet.Error()) + }) + + t.Run("address is present", func(t *testing.T) { + t.Parallel() + + dummyKey := getDummyKey(t) + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + state := gnoland.GnoGenesisState{ + // Set an initial balance value + Balances: []gnoland.Balance{ + { + Address: dummyKey.Address(), + Amount: std.NewCoins(std.NewCoin("ugnot", 100)), + }, + }, + } + genesis.AppState = state + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "remove", + "--genesis-path", + tempGenesis.Name(), + "--address", + dummyKey.Address().String(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the genesis was updated + genesis, loadErr := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, loadErr) + + require.NotNil(t, genesis.AppState) + + state, ok := genesis.AppState.(gnoland.GnoGenesisState) + require.True(t, ok) + + assert.Len(t, state.Balances, 0) + }) + + t.Run("address not present", func(t *testing.T) { + t.Parallel() + + dummyKey := getDummyKey(t) + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + state := gnoland.GnoGenesisState{ + Balances: []gnoland.Balance{}, // Empty initial balance + } + genesis.AppState = state + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "balances", + "remove", + "--genesis-path", + tempGenesis.Name(), + "--address", + dummyKey.Address().String(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.ErrorContains(t, cmdErr, errBalanceNotFound.Error()) + }) +} diff --git a/gno.land/cmd/genesis/generate.go b/gno.land/cmd/genesis/generate.go new file mode 100644 index 00000000000..684f8441874 --- /dev/null +++ b/gno.land/cmd/genesis/generate.go @@ -0,0 +1,153 @@ +package main + +import ( + "context" + "flag" + "fmt" + "time" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +var defaultChainID = "dev" + +type generateCfg struct { + outputPath string + chainID string + genesisTime int64 + blockMaxTxBytes int64 + blockMaxDataBytes int64 + blockMaxGas int64 + blockTimeIota int64 +} + +// newGenerateCmd creates the genesis generate subcommand +func newGenerateCmd(io commands.IO) *commands.Command { + cfg := &generateCfg{} + + return commands.NewCommand( + commands.Metadata{ + Name: "generate", + ShortUsage: "generate [flags]", + LongHelp: "Generates a node's genesis.json based on specified parameters", + ShortHelp: "Generates a fresh genesis.json", + }, + cfg, + func(_ context.Context, _ []string) error { + return execGenerate(cfg, io) + }, + ) +} + +func (c *generateCfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.outputPath, + "output-path", + "./genesis.json", + "the output path for the genesis.json", + ) + + fs.Int64Var( + &c.genesisTime, + "genesis-time", + time.Now().Unix(), + "the genesis creation time. Defaults to current time", + ) + + fs.StringVar( + &c.chainID, + "chain-id", + defaultChainID, + "the ID of the chain", + ) + + fs.Int64Var( + &c.blockMaxTxBytes, + "block-max-tx-bytes", + types.MaxBlockTxBytes, + "the max size of the block transaction", + ) + + fs.Int64Var( + &c.blockMaxDataBytes, + "block-max-data-bytes", + types.MaxBlockDataBytes, + "the max size of the block data", + ) + + fs.Int64Var( + &c.blockMaxGas, + "block-max-gas", + types.MaxBlockMaxGas, + "the max gas limit for the block", + ) + + fs.Int64Var( + &c.blockTimeIota, + "block-time-iota", + types.BlockTimeIotaMS, + "the block time iota (in ms)", + ) +} + +func execGenerate(cfg *generateCfg, io commands.IO) error { + // Start with the default configuration + genesis := getDefaultGenesis() + + // Set the genesis time + if cfg.genesisTime > 0 { + genesis.GenesisTime = time.Unix(cfg.genesisTime, 0) + } + + // Set the chain ID + if cfg.chainID != "" { + genesis.ChainID = cfg.chainID + } + + // Set the max tx bytes + if cfg.blockMaxTxBytes > 0 { + genesis.ConsensusParams.Block.MaxTxBytes = cfg.blockMaxTxBytes + } + + // Set the max data bytes + if cfg.blockMaxDataBytes > 0 { + genesis.ConsensusParams.Block.MaxDataBytes = cfg.blockMaxDataBytes + } + + // Set the max block gas + if cfg.blockMaxGas > 0 { + genesis.ConsensusParams.Block.MaxGas = cfg.blockMaxGas + } + + // Set the block time IOTA + if cfg.blockTimeIota > 0 { + genesis.ConsensusParams.Block.TimeIotaMS = cfg.blockTimeIota + } + + // Validate the genesis + if validateErr := genesis.ValidateAndComplete(); validateErr != nil { + return fmt.Errorf("unable to validate genesis, %w", validateErr) + } + + // Save the genesis file to disk + if saveErr := genesis.SaveAs(cfg.outputPath); saveErr != nil { + return fmt.Errorf("unable to save genesis, %w", saveErr) + } + + io.Printfln("Genesis successfully generated at %s\n", cfg.outputPath) + + // Log the empty validator set warning + io.Printfln("WARN: Genesis is generated with an empty validator set") + + return nil +} + +// getDefaultGenesis returns the default genesis config +func getDefaultGenesis() *types.GenesisDoc { + return &types.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: defaultChainID, + ConsensusParams: types.DefaultConsensusParams(), + } +} diff --git a/gno.land/cmd/genesis/generate_test.go b/gno.land/cmd/genesis/generate_test.go new file mode 100644 index 00000000000..ca742e55150 --- /dev/null +++ b/gno.land/cmd/genesis/generate_test.go @@ -0,0 +1,245 @@ +package main + +import ( + "context" + "fmt" + "path/filepath" + "testing" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenesis_Generate(t *testing.T) { + t.Parallel() + + t.Run("default genesis", func(t *testing.T) { + t.Parallel() + + tempDir, cleanup := testutils.NewTestCaseDir(t) + t.Cleanup(cleanup) + + genesisPath := filepath.Join(tempDir, "genesis.json") + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "generate", + "--output-path", + genesisPath, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Load the genesis + genesis, readErr := types.GenesisDocFromFile(genesisPath) + require.NoError(t, readErr) + + // Make sure the default configuration is set + defaultGenesis := getDefaultGenesis() + defaultGenesis.GenesisTime = genesis.GenesisTime + + assert.Equal(t, defaultGenesis, genesis) + }) + + t.Run("set chain ID", func(t *testing.T) { + t.Parallel() + + chainID := "example-chain-ID" + + tempDir, cleanup := testutils.NewTestCaseDir(t) + t.Cleanup(cleanup) + + genesisPath := filepath.Join(tempDir, "genesis.json") + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "generate", + "--chain-id", + chainID, + "--output-path", + genesisPath, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Load the genesis + genesis, readErr := types.GenesisDocFromFile(genesisPath) + require.NoError(t, readErr) + + assert.Equal(t, genesis.ChainID, chainID) + }) + + t.Run("set block max tx bytes", func(t *testing.T) { + t.Parallel() + + blockMaxTxBytes := int64(100) + + tempDir, cleanup := testutils.NewTestCaseDir(t) + t.Cleanup(cleanup) + + genesisPath := filepath.Join(tempDir, "genesis.json") + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "generate", + "--block-max-tx-bytes", + fmt.Sprintf("%d", blockMaxTxBytes), + "--output-path", + genesisPath, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Load the genesis + genesis, readErr := types.GenesisDocFromFile(genesisPath) + require.NoError(t, readErr) + + assert.Equal( + t, + genesis.ConsensusParams.Block.MaxTxBytes, + blockMaxTxBytes, + ) + }) + + t.Run("set block max data bytes", func(t *testing.T) { + t.Parallel() + + blockMaxDataBytes := int64(100) + + tempDir, cleanup := testutils.NewTestCaseDir(t) + t.Cleanup(cleanup) + + genesisPath := filepath.Join(tempDir, "genesis.json") + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "generate", + "--block-max-data-bytes", + fmt.Sprintf("%d", blockMaxDataBytes), + "--output-path", + genesisPath, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Load the genesis + genesis, readErr := types.GenesisDocFromFile(genesisPath) + require.NoError(t, readErr) + + assert.Equal( + t, + genesis.ConsensusParams.Block.MaxDataBytes, + blockMaxDataBytes, + ) + }) + + t.Run("set block max gas", func(t *testing.T) { + t.Parallel() + + blockMaxGas := int64(100) + + tempDir, cleanup := testutils.NewTestCaseDir(t) + t.Cleanup(cleanup) + + genesisPath := filepath.Join(tempDir, "genesis.json") + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "generate", + "--block-max-gas", + fmt.Sprintf("%d", blockMaxGas), + "--output-path", + genesisPath, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Load the genesis + genesis, readErr := types.GenesisDocFromFile(genesisPath) + require.NoError(t, readErr) + + assert.Equal( + t, + genesis.ConsensusParams.Block.MaxGas, + blockMaxGas, + ) + }) + + t.Run("set block time iota", func(t *testing.T) { + t.Parallel() + + blockTimeIota := int64(10) + + tempDir, cleanup := testutils.NewTestCaseDir(t) + t.Cleanup(cleanup) + + genesisPath := filepath.Join(tempDir, "genesis.json") + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "generate", + "--block-time-iota", + fmt.Sprintf("%d", blockTimeIota), + "--output-path", + genesisPath, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Load the genesis + genesis, readErr := types.GenesisDocFromFile(genesisPath) + require.NoError(t, readErr) + + assert.Equal( + t, + genesis.ConsensusParams.Block.TimeIotaMS, + blockTimeIota, + ) + }) + + t.Run("invalid genesis config (chain ID)", func(t *testing.T) { + t.Parallel() + + invalidChainID := "thischainidisunusuallylongsoitwillcausethetesttofail" + + tempDir, cleanup := testutils.NewTestCaseDir(t) + t.Cleanup(cleanup) + + genesisPath := filepath.Join(tempDir, "genesis.json") + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "generate", + "--chain-id", + invalidChainID, + "--output-path", + genesisPath, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.Error(t, cmdErr) + }) +} diff --git a/gno.land/cmd/genesis/main.go b/gno.land/cmd/genesis/main.go new file mode 100644 index 00000000000..507d004e8ed --- /dev/null +++ b/gno.land/cmd/genesis/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + + "github.com/gnolang/gno/tm2/pkg/commands" +) + +func main() { + io := commands.NewDefaultIO() + cmd := newRootCmd(io) + + if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%+v\n", err) + + os.Exit(1) + } +} + +func newRootCmd(io commands.IO) *commands.Command { + cmd := commands.NewCommand( + commands.Metadata{ + ShortUsage: " [flags] [...]", + LongHelp: "Gno Genesis manipulation suite", + }, + commands.NewEmptyConfig(), + commands.HelpExec, + ) + + cmd.AddSubCommands( + newGenerateCmd(io), + newValidatorCmd(io), + newVerifyCmd(io), + newBalancesCmd(io), + newTxsCmd(io), + ) + + return cmd +} + +// commonCfg is the common +// configuration for genesis commands +// that require a genesis.json +type commonCfg struct { + genesisPath string +} + +func (c *commonCfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.genesisPath, + "genesis-path", + "./genesis.json", + "the path to the genesis.json", + ) +} diff --git a/gno.land/cmd/genesis/txs.go b/gno.land/cmd/genesis/txs.go new file mode 100644 index 00000000000..a292a9c01de --- /dev/null +++ b/gno.land/cmd/genesis/txs.go @@ -0,0 +1,39 @@ +package main + +import ( + "flag" + + "github.com/gnolang/gno/tm2/pkg/commands" +) + +type txsCfg struct { + commonCfg +} + +// newTxsCmd creates the genesis txs subcommand +func newTxsCmd(io commands.IO) *commands.Command { + cfg := &txsCfg{} + + cmd := commands.NewCommand( + commands.Metadata{ + Name: "txs", + ShortUsage: "txs [flags]", + ShortHelp: "Manages the initial genesis transactions", + LongHelp: "Manages genesis transactions through input files", + }, + cfg, + commands.HelpExec, + ) + + cmd.AddSubCommands( + newTxsAddCmd(cfg, io), + newTxsRemoveCmd(cfg, io), + newTxsExportCmd(cfg, io), + ) + + return cmd +} + +func (c *txsCfg) RegisterFlags(fs *flag.FlagSet) { + c.commonCfg.RegisterFlags(fs) +} diff --git a/gno.land/cmd/genesis/txs_add.go b/gno.land/cmd/genesis/txs_add.go new file mode 100644 index 00000000000..e356badc4aa --- /dev/null +++ b/gno.land/cmd/genesis/txs_add.go @@ -0,0 +1,141 @@ +package main + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "os" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/std" +) + +var ( + errInvalidTxsFile = errors.New("unable to open transactions file") + errNoTxsFileSpecified = errors.New("no txs file specified") + errTxsParsingAborted = errors.New("transaction parsing aborted") +) + +// newTxsAddCmd creates the genesis txs add subcommand +func newTxsAddCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "add", + ShortUsage: "txs add ", + ShortHelp: "Imports transactions into the genesis.json", + LongHelp: "Imports the transactions from a tx-archive backup to the genesis.json", + }, + commands.NewEmptyConfig(), + func(ctx context.Context, args []string) error { + return execTxsAdd(ctx, txsCfg, io, args) + }, + ) +} + +func execTxsAdd( + ctx context.Context, + cfg *txsCfg, + io commands.IO, + args []string, +) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + // Open the transactions files + if len(args) == 0 { + return errNoTxsFileSpecified + } + + parsedTxs := make([]std.Tx, 0) + for _, file := range args { + file, loadErr := os.Open(file) + if loadErr != nil { + return fmt.Errorf("%w, %w", errInvalidTxsFile, loadErr) + } + + txs, err := getTransactionsFromFile(ctx, file) + if err != nil { + return fmt.Errorf("unable to read file, %w", err) + } + + parsedTxs = append(parsedTxs, txs...) + } + + // Initialize the app state if it's not present + if genesis.AppState == nil { + genesis.AppState = gnoland.GnoGenesisState{} + } + + state := genesis.AppState.(gnoland.GnoGenesisState) + + // Left merge the transactions + fileTxStore := txStore(parsedTxs) + genesisTxStore := txStore(state.Txs) + + // The genesis transactions have preference with the order + // in the genesis.json + if err := genesisTxStore.leftMerge(fileTxStore); err != nil { + return err + } + + // Save the state + state.Txs = genesisTxStore + genesis.AppState = state + + // Save the updated genesis + if err := genesis.SaveAs(cfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + io.Printfln( + "Saved %d transactions to genesis.json", + len(parsedTxs), + ) + + return nil +} + +// getTransactionsFromFile fetches the transactions from the +// specified reader +func getTransactionsFromFile(ctx context.Context, reader io.Reader) ([]std.Tx, error) { + txs := make([]std.Tx, 0) + + scanner := bufio.NewScanner(reader) + + for scanner.Scan() { + select { + case <-ctx.Done(): + return nil, errTxsParsingAborted + default: + // Parse the amino JSON + var tx std.Tx + + if err := amino.UnmarshalJSON(scanner.Bytes(), &tx); err != nil { + return nil, fmt.Errorf( + "unable to unmarshal amino JSON, %w", + err, + ) + } + + txs = append(txs, tx) + } + } + + // Check for scanning errors + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf( + "error encountered while reading file, %w", + err, + ) + } + + return txs, nil +} diff --git a/gno.land/cmd/genesis/txs_add_test.go b/gno.land/cmd/genesis/txs_add_test.go new file mode 100644 index 00000000000..7d194182fb0 --- /dev/null +++ b/gno.land/cmd/genesis/txs_add_test.go @@ -0,0 +1,266 @@ +package main + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateDummyTxs generates dummy transactions +func generateDummyTxs(t *testing.T, count int) []std.Tx { + t.Helper() + + txs := make([]std.Tx, count) + + for i := 0; i < count; i++ { + txs[i] = std.Tx{ + Msgs: []std.Msg{ + bank.MsgSend{ + FromAddress: crypto.Address{byte(i)}, + ToAddress: crypto.Address{byte((i + 1) % count)}, + Amount: std.NewCoins(std.NewCoin("ugnot", 1)), + }, + }, + Fee: std.Fee{ + GasWanted: 1, + GasFee: std.NewCoin("ugnot", 1000000), + }, + Memo: fmt.Sprintf("tx %d", i), + } + } + + return txs +} + +// encodeDummyTxs encodes the transactions into amino JSON +func encodeDummyTxs(t *testing.T, txs []std.Tx) []string { + t.Helper() + + encodedTxs := make([]string, 0, len(txs)) + + for _, tx := range txs { + encodedTx, err := amino.MarshalJSON(tx) + if err != nil { + t.Fatalf("unable to marshal tx, %v", err) + } + + encodedTxs = append(encodedTxs, string(encodedTx)) + } + + return encodedTxs +} + +func TestGenesis_Txs_Add(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis file", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "add", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("invalid txs file", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "add", + "--genesis-path", + tempGenesis.Name(), + "dummy-tx-file", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errInvalidTxsFile.Error()) + }) + + t.Run("no txs file", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "add", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errNoTxsFileSpecified.Error()) + }) + + t.Run("malformed txs file", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "add", + "--genesis-path", + tempGenesis.Name(), + tempGenesis.Name(), // invalid txs file + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, "unable to read file") + }) + + t.Run("valid txs file", func(t *testing.T) { + t.Parallel() + + // Generate dummy txs + txs := generateDummyTxs(t, 10) + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Prepare the transactions file + txsFile, txsCleanup := testutils.NewTestFile(t) + t.Cleanup(txsCleanup) + + _, err := txsFile.WriteString( + strings.Join( + encodeDummyTxs(t, txs), + "\n", + ), + ) + require.NoError(t, err) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "add", + "--genesis-path", + tempGenesis.Name(), + txsFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the transactions were written down + updatedGenesis, err := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, err) + require.NotNil(t, updatedGenesis.AppState) + + // Fetch the state + state := updatedGenesis.AppState.(gnoland.GnoGenesisState) + + assert.Len(t, state.Txs, len(txs)) + + for index, tx := range state.Txs { + assert.Equal(t, txs[index], tx) + } + }) + + t.Run("existing genesis txs", func(t *testing.T) { + t.Parallel() + + // Generate dummy txs + txs := generateDummyTxs(t, 10) + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + genesisState := gnoland.GnoGenesisState{ + Txs: txs[0 : len(txs)/2], + } + + genesis.AppState = genesisState + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Prepare the transactions file + txsFile, txsCleanup := testutils.NewTestFile(t) + t.Cleanup(txsCleanup) + + _, err := txsFile.WriteString( + strings.Join( + encodeDummyTxs(t, txs), + "\n", + ), + ) + require.NoError(t, err) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "add", + "--genesis-path", + tempGenesis.Name(), + txsFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the transactions were written down + updatedGenesis, err := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, err) + require.NotNil(t, updatedGenesis.AppState) + + // Fetch the state + state := updatedGenesis.AppState.(gnoland.GnoGenesisState) + + assert.Len(t, state.Txs, len(txs)) + + for index, tx := range state.Txs { + assert.Equal(t, txs[index], tx) + } + }) +} diff --git a/gno.land/cmd/genesis/txs_export.go b/gno.land/cmd/genesis/txs_export.go new file mode 100644 index 00000000000..b06660d87d4 --- /dev/null +++ b/gno.land/cmd/genesis/txs_export.go @@ -0,0 +1,92 @@ +package main + +import ( + "context" + "errors" + "fmt" + "os" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +var errNoOutputFile = errors.New("no output file path specified") + +// newTxsExportCmd creates the genesis txs export subcommand +func newTxsExportCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "export", + ShortUsage: "txs export [flags] ", + ShortHelp: "Exports the transactions from the genesis.json", + LongHelp: "Exports the transactions from the genesis.json to an output file", + }, + commands.NewEmptyConfig(), + func(_ context.Context, args []string) error { + return execTxsExport(txsCfg, io, args) + }, + ) +} + +func execTxsExport(cfg *txsCfg, io commands.IO, args []string) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + // Load the genesis state + if genesis.AppState == nil { + return errAppStateNotSet + } + + state := genesis.AppState.(gnoland.GnoGenesisState) + if len(state.Txs) == 0 { + io.Println("No genesis transactions to export") + + return nil + } + + // Make sure the output file path is specified + if len(args) == 0 { + return errNoOutputFile + } + + // Open output file + outputFile, err := os.OpenFile( + args[0], + os.O_RDWR|os.O_CREATE|os.O_APPEND, + 0o755, + ) + if err != nil { + return fmt.Errorf("unable to create output file, %w", err) + } + + // Save the transactions + for _, tx := range state.Txs { + // Marshal tx individual tx into JSON + jsonData, err := amino.MarshalJSON(tx) + if err != nil { + return fmt.Errorf("unable to marshal JSON data, %w", err) + } + + // Write the JSON data as a line to the file + if _, err = outputFile.Write(jsonData); err != nil { + return fmt.Errorf("unable to write to output, %w", err) + } + + // Write a newline character to separate JSON objects + if _, err = outputFile.WriteString("\n"); err != nil { + return fmt.Errorf("unable to write newline output, %w", err) + } + } + + io.Printfln( + "Exported %d transactions", + len(state.Txs), + ) + + return nil +} diff --git a/gno.land/cmd/genesis/txs_export_test.go b/gno.land/cmd/genesis/txs_export_test.go new file mode 100644 index 00000000000..bc84bc45f73 --- /dev/null +++ b/gno.land/cmd/genesis/txs_export_test.go @@ -0,0 +1,140 @@ +package main + +import ( + "bufio" + "context" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenesis_Txs_Export(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis file", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "export", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("invalid genesis app state", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + genesis.AppState = nil // no app state + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "export", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errAppStateNotSet.Error()) + }) + + t.Run("no output file specified", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + genesis.AppState = gnoland.GnoGenesisState{ + Txs: generateDummyTxs(t, 1), + } + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "export", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errNoOutputFile.Error()) + }) + + t.Run("valid txs export", func(t *testing.T) { + t.Parallel() + + // Generate dummy txs + txs := generateDummyTxs(t, 10) + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + genesis.AppState = gnoland.GnoGenesisState{ + Txs: txs, + } + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Prepare the output file + outputFile, outputCleanup := testutils.NewTestFile(t) + t.Cleanup(outputCleanup) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "export", + "--genesis-path", + tempGenesis.Name(), + outputFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the transactions were written down + scanner := bufio.NewScanner(outputFile) + + outputTxs := make([]std.Tx, 0) + for scanner.Scan() { + var tx std.Tx + + require.NoError(t, amino.UnmarshalJSON(scanner.Bytes(), &tx)) + + outputTxs = append(outputTxs, tx) + } + + require.NoError(t, scanner.Err()) + + assert.Len(t, outputTxs, len(txs)) + + for index, tx := range outputTxs { + assert.Equal(t, txs[index], tx) + } + }) +} diff --git a/gno.land/cmd/genesis/txs_remove.go b/gno.land/cmd/genesis/txs_remove.go new file mode 100644 index 00000000000..7893ad50cac --- /dev/null +++ b/gno.land/cmd/genesis/txs_remove.go @@ -0,0 +1,108 @@ +package main + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/std" +) + +var ( + errAppStateNotSet = errors.New("genesis app state not set") + errNoTxHashSpecified = errors.New("no transaction hashes specified") + errTxNotFound = errors.New("transaction not present in genesis.json") +) + +// newTxsRemoveCmd creates the genesis txs remove subcommand +func newTxsRemoveCmd(txsCfg *txsCfg, io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "remove", + ShortUsage: "txs remove ", + ShortHelp: "Removes the transactions from the genesis.json", + LongHelp: "Removes the transactions using the transaction hash", + }, + commands.NewEmptyConfig(), + func(_ context.Context, args []string) error { + return execTxsRemove(txsCfg, io, args) + }, + ) +} + +func execTxsRemove(cfg *txsCfg, io commands.IO, args []string) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + // Check if the genesis state is set at all + if genesis.AppState == nil { + return errAppStateNotSet + } + + // Make sure the transaction hashes are set + if len(args) == 0 { + return errNoTxHashSpecified + } + + state := genesis.AppState.(gnoland.GnoGenesisState) + + for _, inputHash := range args { + index := -1 + + for indx, tx := range state.Txs { + // Find the hash of the transaction + hash, err := getTxHash(tx) + if err != nil { + return fmt.Errorf("unable to generate tx hash, %w", err) + } + + // Check if the hashes match + if strings.ToLower(hash) == strings.ToLower(inputHash) { + index = indx + + break + } + } + + if index < 0 { + return errTxNotFound + } + + state.Txs = append(state.Txs[:index], state.Txs[index+1:]...) + + io.Printfln( + "Transaction %s removed from genesis.json", + inputHash, + ) + } + + genesis.AppState = state + + // Save the updated genesis + if err := genesis.SaveAs(cfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + return nil +} + +// getTxHash returns the hex hash representation of +// the transaction (Amino encoded) +func getTxHash(tx std.Tx) (string, error) { + encodedTx, err := amino.Marshal(tx) + if err != nil { + return "", fmt.Errorf("unable to marshal transaction, %w", err) + } + + txHash := types.Tx(encodedTx).Hash() + + return fmt.Sprintf("%X", txHash), nil +} diff --git a/gno.land/cmd/genesis/txs_remove_test.go b/gno.land/cmd/genesis/txs_remove_test.go new file mode 100644 index 00000000000..b89f2af761a --- /dev/null +++ b/gno.land/cmd/genesis/txs_remove_test.go @@ -0,0 +1,136 @@ +package main + +import ( + "context" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenesis_Txs_Remove(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis file", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "remove", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("invalid genesis app state", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + genesis.AppState = nil // no app state + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "remove", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errAppStateNotSet.Error()) + }) + t.Run("no transaction hash specified", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + // Generate dummy txs + txs := generateDummyTxs(t, 10) + + genesis := getDefaultGenesis() + genesis.AppState = gnoland.GnoGenesisState{ + Txs: txs, + } + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "remove", + "--genesis-path", + tempGenesis.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errNoTxHashSpecified.Error()) + }) + + t.Run("transaction removed", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + // Generate dummy txs + txs := generateDummyTxs(t, 10) + + genesis := getDefaultGenesis() + genesis.AppState = gnoland.GnoGenesisState{ + Txs: txs, + } + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + txHash, err := getTxHash(txs[0]) + require.NoError(t, err) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "txs", + "remove", + "--genesis-path", + tempGenesis.Name(), + txHash, + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + + // Validate the transaction was removed + updatedGenesis, err := types.GenesisDocFromFile(tempGenesis.Name()) + require.NoError(t, err) + require.NotNil(t, updatedGenesis.AppState) + + // Fetch the state + state := updatedGenesis.AppState.(gnoland.GnoGenesisState) + + assert.Len(t, state.Txs, len(txs)-1) + + for _, tx := range state.Txs { + genesisTxHash, err := getTxHash(tx) + require.NoError(t, err) + + assert.NotEqual(t, txHash, genesisTxHash) + } + }) +} diff --git a/gno.land/cmd/genesis/types.go b/gno.land/cmd/genesis/types.go new file mode 100644 index 00000000000..dba39ea8ec1 --- /dev/null +++ b/gno.land/cmd/genesis/types.go @@ -0,0 +1,61 @@ +package main + +import ( + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/std" +) + +// txStore is a wrapper for TM2 transactions +type txStore []std.Tx + +// leftMerge merges the two tx stores, with +// preference to the left +func (i *txStore) leftMerge(b txStore) error { + // Build out the tx hash map + txHashMap := make(map[string]struct{}, len(*i)) + + for _, tx := range *i { + txHash, err := getTxHash(tx) + if err != nil { + return err + } + + txHashMap[txHash] = struct{}{} + } + + for _, tx := range b { + txHash, err := getTxHash(tx) + if err != nil { + return err + } + + if _, exists := txHashMap[txHash]; !exists { + *i = append(*i, tx) + } + } + + return nil +} + +type accountBalances map[types.Address]gnoland.Balance // address -> balance (ugnot) + +// toList linearizes the account balances map +func (a accountBalances) toList() []gnoland.Balance { + balances := make([]gnoland.Balance, 0, len(a)) + + for _, balance := range a { + balances = append(balances, balance) + } + + return balances +} + +// leftMerge left-merges the two maps +func (a accountBalances) leftMerge(b accountBalances) { + for key, bVal := range b { + if _, present := (a)[key]; !present { + (a)[key] = bVal + } + } +} diff --git a/gno.land/cmd/genesis/validator.go b/gno.land/cmd/genesis/validator.go new file mode 100644 index 00000000000..bf858a7732e --- /dev/null +++ b/gno.land/cmd/genesis/validator.go @@ -0,0 +1,49 @@ +package main + +import ( + "flag" + + "github.com/gnolang/gno/tm2/pkg/commands" +) + +type validatorCfg struct { + commonCfg + + address string +} + +// newValidatorCmd creates the genesis validator subcommand +func newValidatorCmd(io commands.IO) *commands.Command { + cfg := &validatorCfg{ + commonCfg: commonCfg{}, + } + + cmd := commands.NewCommand( + commands.Metadata{ + Name: "validator", + ShortUsage: "validator [flags]", + LongHelp: "Manipulates the genesis.json validator set", + ShortHelp: "Validator set management in genesis.json", + }, + cfg, + commands.HelpExec, + ) + + cmd.AddSubCommands( + newValidatorAddCmd(cfg, io), + newValidatorRemoveCmd(cfg, io), + ) + + return cmd +} + +func (c *validatorCfg) RegisterFlags(fs *flag.FlagSet) { + c.commonCfg.RegisterFlags(fs) + + fs.StringVar( + &c.address, + "address", + "", + "the output path for the genesis.json", + ) +} diff --git a/gno.land/cmd/genesis/validator_add.go b/gno.land/cmd/genesis/validator_add.go new file mode 100644 index 00000000000..502b572effb --- /dev/null +++ b/gno.land/cmd/genesis/validator_add.go @@ -0,0 +1,137 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" + _ "github.com/gnolang/gno/tm2/pkg/crypto/keys" +) + +var ( + errInvalidPower = errors.New("invalid validator power") + errInvalidName = errors.New("invalid validator name") + errPublicKeyMismatch = errors.New("provided public key and address do not match") + errAddressPresent = errors.New("validator with same address already present in genesis.json") +) + +type validatorAddCfg struct { + rootCfg *validatorCfg + + pubKey string + name string + power int64 +} + +// newValidatorAddCmd creates the genesis validator add subcommand +func newValidatorAddCmd(validatorCfg *validatorCfg, io commands.IO) *commands.Command { + cfg := &validatorAddCfg{ + rootCfg: validatorCfg, + } + + return commands.NewCommand( + commands.Metadata{ + Name: "add", + ShortUsage: "validator add [flags]", + LongHelp: "Adds a new validator to the genesis.json", + }, + cfg, + func(_ context.Context, _ []string) error { + return execValidatorAdd(cfg, io) + }, + ) +} + +func (c *validatorAddCfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.pubKey, + "pub-key", + "", + "the bech32 string representation of the validator's public key", + ) + + fs.StringVar( + &c.name, + "name", + "", + "the name of the validator (must be unique)", + ) + + fs.Int64Var( + &c.power, + "power", + 1, + "the voting power of the validator (must be > 0)", + ) +} + +func execValidatorAdd(cfg *validatorAddCfg, io commands.IO) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.rootCfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + // Check the validator address + address, err := crypto.AddressFromString(cfg.rootCfg.address) + if err != nil { + return fmt.Errorf("invalid validator address, %w", err) + } + + // Check the voting power + if cfg.power < 1 { + return errInvalidPower + } + + // Check the name + if cfg.name == "" { + return errors.New("invalid validator name") + } + + // Check the public key + pubKey, err := crypto.PubKeyFromBech32(cfg.pubKey) + if err != nil { + return fmt.Errorf("invalid validator public key, %w", err) + } + + // Check the public key matches the address + if pubKey.Address() != address { + return errors.New("provided public key and address do not match") + } + + validator := types.GenesisValidator{ + Address: address, + PubKey: pubKey, + Power: cfg.power, + Name: cfg.name, + } + + // Check if the validator exists + for _, genesisValidator := range genesis.Validators { + // There is no need to check if the public keys match + // since the address is derived from it, and the derivation + // is checked already + if validator.Address == genesisValidator.Address { + return errAddressPresent + } + } + + // Add the validator + genesis.Validators = append(genesis.Validators, validator) + + // Save the updated genesis + if err := genesis.SaveAs(cfg.rootCfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + io.Printfln( + "Validator with address %s added to genesis file", + cfg.rootCfg.address, + ) + + return nil +} diff --git a/gno.land/cmd/genesis/validator_add_test.go b/gno.land/cmd/genesis/validator_add_test.go new file mode 100644 index 00000000000..37af4157e7c --- /dev/null +++ b/gno.land/cmd/genesis/validator_add_test.go @@ -0,0 +1,293 @@ +package main + +import ( + "context" + "testing" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/bip39" + "github.com/gnolang/gno/tm2/pkg/crypto/hd" + "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" + "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// getDummyKey generates a random public key, +// and returns the key info +func getDummyKey(t *testing.T) crypto.PubKey { + t.Helper() + + mnemonic, err := client.GenerateMnemonic(256) + require.NoError(t, err) + + seed := bip39.NewSeed(mnemonic, "") + + return generateKeyFromSeed(seed, 0).PubKey() +} + +// generateKeyFromSeed generates a private key from +// the provided seed and index +func generateKeyFromSeed(seed []byte, index uint32) crypto.PrivKey { + pathParams := hd.NewFundraiserParams(0, crypto.CoinType, index) + + masterPriv, ch := hd.ComputeMastersFromSeed(seed) + + //nolint:errcheck // This derivation can never error out, since the path params + // are always going to be valid + derivedPriv, _ := hd.DerivePrivateKeyForPath(masterPriv, ch, pathParams.String()) + + return secp256k1.PrivKeySecp256k1(derivedPriv) +} + +// getDummyKeys generates random keys for testing +func getDummyKeys(t *testing.T, count int) []crypto.PubKey { + t.Helper() + + dummyKeys := make([]crypto.PubKey, count) + + for i := 0; i < count; i++ { + dummyKeys[i] = getDummyKey(t) + } + + return dummyKeys +} + +func TestGenesis_Validator_Add(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis file", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "add", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("invalid validator address", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "add", + "--genesis-path", + tempGenesis.Name(), + "--address", + "dummyaddress", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, "invalid validator address") + }) + + t.Run("invalid voting power", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + key := getDummyKey(t) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "add", + "--genesis-path", + tempGenesis.Name(), + "--address", + key.Address().String(), + "--power", + "-1", // invalid voting power + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorIs(t, cmdErr, errInvalidPower) + }) + + t.Run("invalid validator name", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + key := getDummyKey(t) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "add", + "--genesis-path", + tempGenesis.Name(), + "--address", + key.Address().String(), + "--name", + "", // invalid validator name + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errInvalidName.Error()) + }) + + t.Run("invalid public key", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + key := getDummyKey(t) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "add", + "--genesis-path", + tempGenesis.Name(), + "--address", + key.Address().String(), + "--name", + "example", + "--pub-key", + "invalidkey", // invalid pub key + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, "invalid validator public key") + }) + + t.Run("public key address mismatch", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + dummyKeys := getDummyKeys(t, 2) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "add", + "--genesis-path", + tempGenesis.Name(), + "--address", + dummyKeys[0].Address().String(), + "--name", + "example", + "--pub-key", + crypto.PubKeyToBech32(dummyKeys[1]), // another key + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errPublicKeyMismatch.Error()) + }) + + t.Run("validator with same address exists", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + dummyKeys := getDummyKeys(t, 2) + genesis := getDefaultGenesis() + + // Set an existing validator + genesis.Validators = append(genesis.Validators, types.GenesisValidator{ + Address: dummyKeys[0].Address(), + PubKey: dummyKeys[0], + Power: 1, + Name: "example", + }) + + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "add", + "--genesis-path", + tempGenesis.Name(), + "--address", + dummyKeys[0].Address().String(), + "--name", + "example", + "--pub-key", + crypto.PubKeyToBech32(dummyKeys[0]), // another key + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errAddressPresent.Error()) + }) + + t.Run("valid genesis validator", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + key := getDummyKey(t) + genesis := getDefaultGenesis() + + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "add", + "--genesis-path", + tempGenesis.Name(), + "--address", + key.Address().String(), + "--name", + "example", + "--pub-key", + crypto.PubKeyToBech32(key), // another key + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + }) +} diff --git a/gno.land/cmd/genesis/validator_remove.go b/gno.land/cmd/genesis/validator_remove.go new file mode 100644 index 00000000000..960d891239b --- /dev/null +++ b/gno.land/cmd/genesis/validator_remove.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "errors" + "fmt" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" +) + +var errValidatorNotPresent = errors.New("validator not present in genesis.json") + +// newValidatorRemoveCmd creates the genesis validator remove subcommand +func newValidatorRemoveCmd(rootCfg *validatorCfg, io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "remove", + ShortUsage: "validator remove [flags]", + LongHelp: "Removes a validator from the genesis.json", + }, + commands.NewEmptyConfig(), + func(_ context.Context, _ []string) error { + return execValidatorRemove(rootCfg, io) + }, + ) +} + +func execValidatorRemove(cfg *validatorCfg, io commands.IO) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + // Check the validator address + address, err := crypto.AddressFromString(cfg.address) + if err != nil { + return fmt.Errorf("invalid validator address, %w", err) + } + + index := -1 + + for indx, validator := range genesis.Validators { + if validator.Address == address { + index = indx + + break + } + } + + if index < 0 { + return errors.New("validator not present in genesis.json") + } + + // Drop the validator + genesis.Validators = append(genesis.Validators[:index], genesis.Validators[index+1:]...) + + // Save the updated genesis + if err := genesis.SaveAs(cfg.genesisPath); err != nil { + return fmt.Errorf("unable to save genesis.json, %w", err) + } + + io.Printfln( + "Validator with address %s removed from genesis file", + cfg.address, + ) + + return nil +} diff --git a/gno.land/cmd/genesis/validator_remove_test.go b/gno.land/cmd/genesis/validator_remove_test.go new file mode 100644 index 00000000000..953657afe33 --- /dev/null +++ b/gno.land/cmd/genesis/validator_remove_test.go @@ -0,0 +1,129 @@ +package main + +import ( + "context" + "testing" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenesis_Validator_Remove(t *testing.T) { + t.Parallel() + + t.Run("invalid genesis file", func(t *testing.T) { + t.Parallel() + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "remove", + "--genesis-path", + "dummy-path", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errUnableToLoadGenesis.Error()) + }) + + t.Run("invalid validator address", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + genesis := getDefaultGenesis() + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "remove", + "--genesis-path", + tempGenesis.Name(), + "--address", + "dummyaddress", + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, "invalid validator address") + }) + + t.Run("validator not found", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + dummyKeys := getDummyKeys(t, 2) + genesis := getDefaultGenesis() + + // Set an existing validator + genesis.Validators = append(genesis.Validators, types.GenesisValidator{ + Address: dummyKeys[0].Address(), + PubKey: dummyKeys[0], + Power: 1, + Name: "example", + }) + + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "remove", + "--genesis-path", + tempGenesis.Name(), + "--address", + dummyKeys[1].Address().String(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorContains(t, cmdErr, errValidatorNotPresent.Error()) + }) + + t.Run("validator removed", func(t *testing.T) { + t.Parallel() + + tempGenesis, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + dummyKey := getDummyKey(t) + + genesis := getDefaultGenesis() + + // Set an existing validator + genesis.Validators = append(genesis.Validators, types.GenesisValidator{ + Address: dummyKey.Address(), + PubKey: dummyKey, + Power: 1, + Name: "example", + }) + + require.NoError(t, genesis.SaveAs(tempGenesis.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "validator", + "remove", + "--genesis-path", + tempGenesis.Name(), + "--address", + dummyKey.Address().String(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.NoError(t, cmdErr) + }) +} diff --git a/gno.land/cmd/genesis/verify.go b/gno.land/cmd/genesis/verify.go new file mode 100644 index 00000000000..260a9eb734d --- /dev/null +++ b/gno.land/cmd/genesis/verify.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +var errInvalidGenesisState = errors.New("invalid genesis state type") + +type verifyCfg struct { + commonCfg +} + +// newVerifyCmd creates the genesis verify subcommand +func newVerifyCmd(io commands.IO) *commands.Command { + cfg := &verifyCfg{} + + return commands.NewCommand( + commands.Metadata{ + Name: "verify", + ShortUsage: "verify [flags]", + LongHelp: "Verifies a node's genesis.json", + ShortHelp: "Verifies a genesis.json", + }, + cfg, + func(_ context.Context, _ []string) error { + return execVerify(cfg, io) + }, + ) +} + +func (c *verifyCfg) RegisterFlags(fs *flag.FlagSet) { + c.commonCfg.RegisterFlags(fs) +} + +func execVerify(cfg *verifyCfg, io commands.IO) error { + // Load the genesis + genesis, loadErr := types.GenesisDocFromFile(cfg.genesisPath) + if loadErr != nil { + return fmt.Errorf("unable to load genesis, %w", loadErr) + } + + // Verify it + if validateErr := genesis.Validate(); validateErr != nil { + return fmt.Errorf("unable to verify genesis, %w", validateErr) + } + + // Validate the genesis state + if genesis.AppState != nil { + state, ok := genesis.AppState.(gnoland.GnoGenesisState) + if !ok { + return errInvalidGenesisState + } + + // Validate the initial transactions + for _, tx := range state.Txs { + if validateErr := tx.ValidateBasic(); validateErr != nil { + return fmt.Errorf("invalid transacton, %w", validateErr) + } + } + + // Validate the initial balances + for _, balance := range state.Balances { + if err := balance.Verify(); err != nil { + return fmt.Errorf("invalid balance: %w", err) + } + } + } + + io.Printfln("Genesis at %s is valid", cfg.genesisPath) + + return nil +} diff --git a/gno.land/cmd/genesis/verify_test.go b/gno.land/cmd/genesis/verify_test.go new file mode 100644 index 00000000000..8388949898b --- /dev/null +++ b/gno.land/cmd/genesis/verify_test.go @@ -0,0 +1,169 @@ +package main + +import ( + "context" + "testing" + "time" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto/mock" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/require" +) + +func TestGenesis_Verify(t *testing.T) { + t.Parallel() + + getValidTestGenesis := func() *types.GenesisDoc { + key := mock.GenPrivKey().PubKey() + + return &types.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: "valid-chain-id", + ConsensusParams: types.DefaultConsensusParams(), + Validators: []types.GenesisValidator{ + { + Address: key.Address(), + PubKey: key, + Power: 1, + Name: "valid validator", + }, + }, + } + } + + t.Run("invalid txs", func(t *testing.T) { + t.Parallel() + + tempFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + g := getValidTestGenesis() + + g.AppState = gnoland.GnoGenesisState{ + Balances: []gnoland.Balance{}, + Txs: []std.Tx{ + {}, + }, + } + + require.NoError(t, g.SaveAs(tempFile.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "verify", + "--genesis-path", + tempFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.Error(t, cmdErr) + }) + + t.Run("invalid balances", func(t *testing.T) { + t.Parallel() + + tempFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + g := getValidTestGenesis() + + g.AppState = gnoland.GnoGenesisState{ + Balances: []gnoland.Balance{ + {}, + }, + Txs: []std.Tx{}, + } + + require.NoError(t, g.SaveAs(tempFile.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "verify", + "--genesis-path", + tempFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.Error(t, cmdErr) + }) + + t.Run("valid genesis", func(t *testing.T) { + t.Parallel() + + tempFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + g := getValidTestGenesis() + g.AppState = gnoland.GnoGenesisState{ + Balances: []gnoland.Balance{}, + Txs: []std.Tx{}, + } + + require.NoError(t, g.SaveAs(tempFile.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "verify", + "--genesis-path", + tempFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + }) + + t.Run("valid genesis, no state", func(t *testing.T) { + t.Parallel() + + tempFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + g := getValidTestGenesis() + require.NoError(t, g.SaveAs(tempFile.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "verify", + "--genesis-path", + tempFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.NoError(t, cmdErr) + }) + + t.Run("invalid genesis state", func(t *testing.T) { + t.Parallel() + + tempFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + g := getValidTestGenesis() + g.AppState = "Totally invalid state" + require.NoError(t, g.SaveAs(tempFile.Name())) + + // Create the command + cmd := newRootCmd(commands.NewTestIO()) + args := []string{ + "verify", + "--genesis-path", + tempFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + require.Error(t, cmdErr) + }) +} diff --git a/gno.land/cmd/gnofaucet/serve.go b/gno.land/cmd/gnofaucet/serve.go index 3fe0966592c..e00406a6e80 100644 --- a/gno.land/cmd/gnofaucet/serve.go +++ b/gno.land/cmd/gnofaucet/serve.go @@ -152,7 +152,7 @@ func (c *config) RegisterFlags(fs *flag.FlagSet) { ) } -func execServe(cfg *config, args []string, io *commands.IO) error { +func execServe(cfg *config, args []string, io commands.IO) error { if len(args) != 1 { return flag.ErrHelp } @@ -345,7 +345,7 @@ func execServe(cfg *config, args []string, io *commands.IO) error { func sendAmountTo( cfg *config, cli rpcclient.Client, - io *commands.IO, + io commands.IO, name, pass string, toAddr crypto.Address, diff --git a/gno.land/cmd/gnoland/integration_test.go b/gno.land/cmd/gnoland/integration_test.go index 78c8e94fa09..37451df9704 100644 --- a/gno.land/cmd/gnoland/integration_test.go +++ b/gno.land/cmd/gnoland/integration_test.go @@ -4,9 +4,8 @@ import ( "testing" "github.com/gnolang/gno/gno.land/pkg/integration" - "github.com/rogpeppe/go-internal/testscript" ) func TestTestdata(t *testing.T) { - testscript.Run(t, integration.SetupGnolandTestScript(t, "testdata")) + integration.RunGnolandTestscripts(t, "testdata") } diff --git a/gno.land/cmd/gnoland/root.go b/gno.land/cmd/gnoland/root.go index cf2a6252478..dfb595347c0 100644 --- a/gno.land/cmd/gnoland/root.go +++ b/gno.land/cmd/gnoland/root.go @@ -11,8 +11,7 @@ import ( ) func main() { - io := commands.NewDefaultIO() - cmd := newRootCmd(io) + cmd := newRootCmd(commands.NewDefaultIO()) if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil { _, _ = fmt.Fprintf(os.Stderr, "%+v\n", err) @@ -20,7 +19,7 @@ func main() { } } -func newRootCmd(io *commands.IO) *commands.Command { +func newRootCmd(io commands.IO) *commands.Command { cmd := commands.NewCommand( commands.Metadata{ ShortUsage: " [flags] [...]", diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 39336004f87..d95e0f03893 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "flag" "fmt" "os" @@ -11,14 +12,14 @@ import ( "time" "github.com/gnolang/gno/gno.land/pkg/gnoland" - vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/telemetry" - "github.com/gnolang/gno/tm2/pkg/amino" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" "github.com/gnolang/gno/tm2/pkg/bft/privval" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/null" + eventstorecfg "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -28,18 +29,23 @@ import ( ) type startCfg struct { + gnoRootDir string skipFailingGenesisTxs bool skipStart bool genesisBalancesFile string genesisTxsFile string chainID string genesisRemote string - rootDir string + dataDir string genesisMaxVMCycles int64 config string + + txEventStoreType string + txEventStorePath string + nodeConfigPath string } -func newStartCmd(io *commands.IO) *commands.Command { +func newStartCmd(io commands.IO) *commands.Command { cfg := &startCfg{} return commands.NewCommand( @@ -49,13 +55,17 @@ func newStartCmd(io *commands.IO) *commands.Command { ShortHelp: "Run the full node", }, cfg, - func(_ context.Context, args []string) error { - return execStart(cfg, args, io) + func(_ context.Context, _ []string) error { + return execStart(cfg, io) }, ) } func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { + gnoroot := gnoland.MustGuessGnoRootDir() + defaultGenesisBalancesFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_balances.txt") + defaultGenesisTxsFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_txs.txt") + fs.BoolVar( &c.skipFailingGenesisTxs, "skip-failing-genesis-txs", @@ -73,14 +83,14 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.genesisBalancesFile, "genesis-balances-file", - "./genesis/genesis_balances.txt", + defaultGenesisBalancesFile, "initial distribution file", ) fs.StringVar( &c.genesisTxsFile, "genesis-txs-file", - "./genesis/genesis_txs.txt", + defaultGenesisTxsFile, "initial txs to replay", ) @@ -92,8 +102,16 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { ) fs.StringVar( - &c.rootDir, - "root-dir", + &c.gnoRootDir, + "gnoroot-dir", + gnoroot, + "the root directory of the gno repository", + ) + + // XXX: Use home directory for this + fs.StringVar( + &c.dataDir, + "data-dir", "testdir", "directory for config and data", ) @@ -116,7 +134,45 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { &c.config, "config", "", - "config file (optional)", + "the flag config file (optional)", + ) + + fs.StringVar( + &c.nodeConfigPath, + "tm2-node-config", + "", + "the node TOML config file path (optional)", + ) + + fs.StringVar( + &c.txEventStoreType, + "tx-event-store-type", + null.EventStoreType, + fmt.Sprintf( + "type of transaction event store [%s]", + strings.Join( + []string{ + null.EventStoreType, + file.EventStoreType, + }, + ", ", + ), + ), + ) + + fs.StringVar( + &c.txEventStorePath, + "tx-event-store-path", + "", + fmt.Sprintf("path for the file tx event store (required if event store is '%s')", file.EventStoreType), + ) + + // XXX(deprecated): use data-dir instead + fs.StringVar( + &c.dataDir, + "root-dir", + "testdir", + "deprecated: use data-dir instead - directory for config and data", ) } @@ -161,30 +217,54 @@ func execStart(c *startCfg, io commands.IO) error { return fmt.Errorf("error initializing telemetry: %w", err) } - // create priv validator first. - // need it to generate genesis.json - newPrivValKey := cfg.PrivValidatorKeyFile() - newPrivValState := cfg.PrivValidatorStateFile() - priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) + var ( + cfg *config.Config + loadCfgErr error + ) + + // Set the node configuration + if c.nodeConfigPath != "" { + // Load the node configuration + // from the specified path + cfg, loadCfgErr = config.LoadConfigFile(c.nodeConfigPath) + } else { + // Load the default node configuration + cfg, loadCfgErr = config.LoadOrMakeConfigWithOptions(dataDir, nil) + } + + if loadCfgErr != nil { + return fmt.Errorf("unable to load node configuration, %w", loadCfgErr) + } + + // Write genesis file if missing. + genesisFilePath := filepath.Join(dataDir, cfg.Genesis) - // write genesis file if missing. - genesisFilePath := filepath.Join(rootDir, cfg.Genesis) if !osm.FileExists(genesisFilePath) { - genDoc := makeGenesisDoc( - priv.GetPubKey(), - c.chainID, - c.genesisBalancesFile, - loadGenesisTxs(c.genesisTxsFile, c.chainID, c.genesisRemote), - ) - writeGenesisFile(genDoc, genesisFilePath) + // Create priv validator first. + // Need it to generate genesis.json + newPrivValKey := cfg.PrivValidatorKeyFile() + newPrivValState := cfg.PrivValidatorStateFile() + priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) + pk := priv.GetPubKey() + + // Generate genesis.json file + if err := generateGenesisFile(genesisFilePath, pk, c); err != nil { + return fmt.Errorf("unable to generate genesis file: %w", err) + } } - // create application and node. - gnoApp, err := gnoland.NewApp(rootDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles) + // Initialize the indexer config + txEventStoreCfg, err := getTxEventStoreConfig(c) if err != nil { - return fmt.Errorf("error in creating new app: %w", err) + return fmt.Errorf("unable to parse indexer config, %w", err) } + cfg.TxEventStore = txEventStoreCfg + // Create application and node. + gnoApp, err := gnoland.NewApp(dataDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles) + if err != nil { + return fmt.Errorf("error in creating new app: %w", err) + } cfg.LocalApp = gnoApp gnoNode, err := node.DefaultNewNode(cfg, logger) @@ -192,11 +272,10 @@ func execStart(c *startCfg, io commands.IO) error { return fmt.Errorf("error in creating node: %w", err) } - fmt.Fprintln(io.Err, "Node created.") + fmt.Fprintln(io.Err(), "Node created.") if c.skipStart { - fmt.Fprintln(io.Err, "'--skip-start' is set. Exiting.") - + io.ErrPrintln("'--skip-start' is set. Exiting.") return nil } @@ -204,151 +283,96 @@ func execStart(c *startCfg, io commands.IO) error { return fmt.Errorf("error in start node: %w", err) } - // run forever osm.TrapSignal(func() { if gnoNode.IsRunning() { _ = gnoNode.Stop() } }) - select {} // run forever + // Run forever + select {} } -// Makes a local test genesis doc with local privValidator. -func makeGenesisDoc( - pvPub crypto.PubKey, - chainID string, - genesisBalancesFile string, - genesisTxs []std.Tx, -) *bft.GenesisDoc { +func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) error { gen := &bft.GenesisDoc{} - gen.GenesisTime = time.Now() - gen.ChainID = chainID + gen.ChainID = c.chainID gen.ConsensusParams = abci.ConsensusParams{ Block: &abci.BlockParams{ // TODO: update limits. - MaxTxBytes: 1000000, // 1MB, - MaxDataBytes: 2000000, // 2MB, - MaxGas: 10000000, // 10M gas - TimeIotaMS: 100, // 100ms + MaxTxBytes: 1_000_000, // 1MB, + MaxDataBytes: 2_000_000, // 2MB, + MaxGas: 10_0000_00, // 10M gas + TimeIotaMS: 100, // 100ms }, } + gen.Validators = []bft.GenesisValidator{ { - Address: pvPub.Address(), - PubKey: pvPub, + Address: pk.Address(), + PubKey: pk, Power: 10, Name: "testvalidator", }, } - // Load distribution. - balances := loadGenesisBalances(genesisBalancesFile) - // debug: for _, balance := range balances { fmt.Println(balance) } - - // Load initial packages from examples. - test1 := crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - txs := []std.Tx{} - - // List initial packages to load from examples. - pkgs, err := gnomod.ListPkgs(filepath.Join("..", "examples")) + // Load balances files + balances, err := gnoland.LoadGenesisBalancesFile(c.genesisBalancesFile) if err != nil { - panic(fmt.Errorf("listing gno packages: %w", err)) + return fmt.Errorf("unable to load genesis balances file %q: %w", c.genesisBalancesFile, err) } - // Sort packages by dependencies. - sortedPkgs, err := pkgs.Sort() + // Load examples folder + examplesDir := filepath.Join(c.gnoRootDir, "examples") + test1 := crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot")) + pkgsTxs, err := gnoland.LoadPackagesFromDir(examplesDir, test1, defaultFee, nil) if err != nil { - panic(fmt.Errorf("sorting packages: %w", err)) + return fmt.Errorf("unable to load examples folder: %w", err) } - // Filter out draft packages. - nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() - - for _, pkg := range nonDraftPkgs { - // open files in directory as MemPackage. - memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) - - var tx std.Tx - tx.Msgs = []std.Msg{ - vmm.MsgAddPackage{ - Creator: test1, - Package: memPkg, - Deposit: nil, - }, - } - tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) - tx.Signatures = make([]std.Signature, len(tx.GetSigners())) - txs = append(txs, tx) + // Load Genesis TXs + genesisTxs, err := gnoland.LoadGenesisTxsFile(c.genesisTxsFile, c.chainID, c.genesisRemote) + if err != nil { + return fmt.Errorf("unable to load genesis txs file: %w", err) } - // load genesis txs from file. - txs = append(txs, genesisTxs...) + genesisTxs = append(pkgsTxs, genesisTxs...) - // construct genesis AppState. + // Construct genesis AppState. gen.AppState = gnoland.GnoGenesisState{ Balances: balances, - Txs: txs, + Txs: genesisTxs, } - return gen -} - -func writeGenesisFile(gen *bft.GenesisDoc, filePath string) { - err := gen.SaveAs(filePath) - if err != nil { - panic(err) - } -} -func loadGenesisTxs( - path string, - chainID string, - genesisRemote string, -) []std.Tx { - txs := []std.Tx{} - txsBz := osm.MustReadFile(path) - txsLines := strings.Split(string(txsBz), "\n") - for _, txLine := range txsLines { - if txLine == "" { - continue // skip empty line - } - - // patch the TX - txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID) - txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote) - - var tx std.Tx - amino.MustUnmarshalJSON([]byte(txLine), &tx) - txs = append(txs, tx) + // Write genesis state + if err := gen.SaveAs(genesisFile); err != nil { + return fmt.Errorf("unable to write genesis file %q: %w", genesisFile, err) } - return txs + return nil } -func loadGenesisBalances(path string) []string { - // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot - balances := []string{} - content := osm.MustReadFile(path) - lines := strings.Split(string(content), "\n") - for _, line := range lines { - line = strings.TrimSpace(line) - - // remove comments. - line = strings.Split(line, "#")[0] - line = strings.TrimSpace(line) - - // skip empty lines. - if line == "" { - continue - } +// getTxEventStoreConfig constructs an event store config from provided user options +func getTxEventStoreConfig(c *startCfg) (*eventstorecfg.Config, error) { + var cfg *eventstorecfg.Config - parts := strings.Split(line, "=") - if len(parts) != 2 { - panic("invalid genesis_balance line: " + line) + switch c.txEventStoreType { + case file.EventStoreType: + if c.txEventStorePath == "" { + return nil, errors.New("unspecified file transaction indexer path") } - balances = append(balances, line) + // Fill out the configuration + cfg = &eventstorecfg.Config{ + EventStoreType: file.EventStoreType, + Params: map[string]any{ + file.Path: c.txEventStorePath, + }, + } + default: + cfg = eventstorecfg.DefaultEventStoreConfig() } - return balances + + return cfg, nil } diff --git a/gno.land/cmd/gnoland/start_test.go b/gno.land/cmd/gnoland/start_test.go index 27ef2f572ea..6606d88ba2e 100644 --- a/gno.land/cmd/gnoland/start_test.go +++ b/gno.land/cmd/gnoland/start_test.go @@ -13,6 +13,8 @@ import ( ) func TestStartInitialize(t *testing.T) { + t.Parallel() + cases := []struct { args []string }{ @@ -23,8 +25,11 @@ func TestStartInitialize(t *testing.T) { os.Chdir(filepath.Join("..", "..")) // go to repo's root dir for _, tc := range cases { + tc := tc name := strings.Join(tc.args, " ") t.Run(name, func(t *testing.T) { + t.Parallel() + mockOut := bytes.NewBufferString("") mockErr := bytes.NewBufferString("") io := commands.NewTestIO() diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar index 5e871b058ac..5f1ee0caf49 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -10,7 +10,11 @@ gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 10000 gnokey maketx call -pkgpath gno.land/r/foobar/bar -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 ## compare render -cmp stdout stdout.golden +stdout '("hello from foo" string)' +stdout 'OK!' +stdout 'GAS WANTED: 2000000' +stdout 'GAS USED: [0-9]+' + -- bar.gno -- package bar @@ -19,8 +23,3 @@ func Render(path string) string { return "hello from foo" } --- stdout.golden -- -("hello from foo" string) -OK! -GAS WANTED: 2000000 -GAS USED: 69163 \ No newline at end of file diff --git a/gno.land/cmd/gnoland/testdata/import.txtar b/gno.land/cmd/gnoland/testdata/import.txtar new file mode 100644 index 00000000000..3ad070654cf --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/import.txtar @@ -0,0 +1,27 @@ +# test that the example packages directory is loaded and usable. + +## start a new node +gnoland start + +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/importtest -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +## execute Render +gnokey maketx call -pkgpath gno.land/r/importtest -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("92054" string)' +stdout OK! + +-- gno.mod -- +module gno.land/r/importtest + +-- import.gno -- +package importtest + +import ( + "gno.land/p/demo/ufmt" +) + +func Render(_ string) string { + return ufmt.Sprintf("%d", 92054) +} + diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index e8a2feac0d7..b080e0b403d 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -18,7 +18,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" - osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gorilla/mux" "github.com/gotuna/gotuna" @@ -32,59 +31,93 @@ const ( qFileStr = "vm/qfile" ) +var startedAt time.Time + var flags struct { - bindAddr string - remoteAddr string - captchaSite string - faucetURL string - viewsDir string - pagesDir string - helpChainID string - helpRemote string + BindAddr string + RemoteAddr string + CaptchaSite string + FaucetURL string + ViewsDir string + HelpChainID string + HelpRemote string + WithAnalytics bool } -var startedAt time.Time - func init() { - flag.StringVar(&flags.remoteAddr, "remote", "127.0.0.1:26657", "remote gnoland node address") - flag.StringVar(&flags.bindAddr, "bind", "127.0.0.1:8888", "server listening address") - flag.StringVar(&flags.captchaSite, "captcha-site", "", "recaptcha site key (if empty, captcha are disabled)") - flag.StringVar(&flags.faucetURL, "faucet-url", "http://localhost:5050", "faucet server URL") - flag.StringVar(&flags.viewsDir, "views-dir", "./cmd/gnoweb/views", "views directory location") - flag.StringVar(&flags.pagesDir, "pages-dir", "./cmd/gnoweb/pages", "pages directory location") - flag.StringVar(&flags.helpChainID, "help-chainid", "dev", "help page's chainid") - flag.StringVar(&flags.helpRemote, "help-remote", "127.0.0.1:26657", "help page's remote addr") + flag.StringVar(&flags.RemoteAddr, "remote", "127.0.0.1:26657", "remote gnoland node address") + flag.StringVar(&flags.BindAddr, "bind", "127.0.0.1:8888", "server listening address") + flag.StringVar(&flags.CaptchaSite, "captcha-site", "", "recaptcha site key (if empty, captcha are disabled)") + flag.StringVar(&flags.FaucetURL, "faucet-url", "http://localhost:5050", "faucet server URL") + flag.StringVar(&flags.ViewsDir, "views-dir", "./cmd/gnoweb/views", "views directory location") // XXX: replace with goembed + flag.StringVar(&flags.HelpChainID, "help-chainid", "dev", "help page's chainid") + flag.StringVar(&flags.HelpRemote, "help-remote", "127.0.0.1:26657", "help page's remote addr") + flag.BoolVar(&flags.WithAnalytics, "with-analytics", false, "enable privacy-first analytics") startedAt = time.Now() } func makeApp() gotuna.App { app := gotuna.App{ - ViewFiles: os.DirFS(flags.viewsDir), + ViewFiles: os.DirFS(flags.ViewsDir), Router: gotuna.NewMuxRouter(), Static: static.EmbeddedStatic, // StaticPrefix: "static/", } - app.Router.Handle("/", handlerHome(app)) - app.Router.Handle("/about", handlerAbout(app)) - app.Router.Handle("/game-of-realms", handlerGor(app)) - app.Router.Handle("/faucet", handlerFaucet(app)) - app.Router.Handle("/r/demo/boards:gnolang/6", handlerRedirect(app)) + + // realm aliases + aliases := map[string]string{ + "/": "/r/gnoland/home", + "/about": "/r/gnoland/pages:p/about", + "/gnolang": "/r/gnoland/pages:p/gnolang", + "/ecosystem": "/r/gnoland/pages:p/ecosystem", + "/partners": "/r/gnoland/pages:p/partners", + "/testnets": "/r/gnoland/pages:p/testnets", + "/start": "/r/gnoland/pages:p/start", + "/game-of-realms": "/r/gnoland/pages:p/gor", // XXX: replace with gor realm + "/events": "/r/gnoland/pages:p/events", // XXX: replace with events realm + } + for from, to := range aliases { + app.Router.Handle(from, handlerRealmAlias(app, to)) + } + // http redirects + redirects := map[string]string{ + "/r/demo/boards:gnolang/6": "/r/demo/boards:gnolang/3", // XXX: temporary + "/blog": "/r/gnoland/blog", + "/gor": "/game-of-realms", + "/grants": "/partners", + "/language": "/gnolang", + "/getting-started": "/start", + } + for from, to := range redirects { + app.Router.Handle(from, handlerRedirect(app, to)) + } + // realm routes // NOTE: see rePathPart. app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}/{filename:(?:.*\\.(?:gno|md|txt)$)?}", handlerRealmFile(app)) app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}", handlerRealmMain(app)) app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}:{querystr:.*}", handlerRealmRender(app)) app.Router.Handle("/p/{filepath:.*}", handlerPackageFile(app)) + + // other + app.Router.Handle("/faucet", handlerFaucet(app)) app.Router.Handle("/static/{path:.+}", handlerStaticFile(app)) app.Router.Handle("/favicon.ico", handlerFavicon(app)) + + // api app.Router.Handle("/status.json", handlerStatusJSON(app)) + + app.Router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := r.RequestURI + handleNotFound(app, path, w, r) + }) return app } func main() { flag.Parse() - fmt.Printf("Running on http://%s\n", flags.bindAddr) + fmt.Printf("Running on http://%s\n", flags.BindAddr) server := &http.Server{ - Addr: flags.bindAddr, + Addr: flags.BindAddr, ReadHeaderTimeout: 60 * time.Second, Handler: makeApp().Router, } @@ -94,50 +127,59 @@ func main() { } } -func handlerHome(app gotuna.App) http.Handler { - md := filepath.Join(flags.pagesDir, "HOME.md") - homeContent := osm.MustReadFile(md) - +// handlerRealmAlias is used to render official pages from realms. +// url is intended to be shorter. +// UX is intended to be more minimalistic. +// A link to the realm realm is added. +func handlerRealmAlias(app gotuna.App, rlmpath string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - app.NewTemplatingEngine(). - Set("Title", "Gno.land Smart Contract Platform Using Gnolang (Gno)"). - Set("Description", "Gno.land is the only smart contract platform using the Gnolang (Gno) programming language, an interpretation of the widely-used Golang (Go)."). - Set("HomeContent", string(homeContent)). - Render(w, r, "home.html", "funcs.html") - }) -} - -func handlerAbout(app gotuna.App) http.Handler { - md := filepath.Join(flags.pagesDir, "ABOUT.md") - mainContent := osm.MustReadFile(md) - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - app.NewTemplatingEngine(). - Set("Title", "Gno.land Is A Platform To Write Smart Contracts In Gnolang (Gno)"). - Set("Description", "On Gno.land, developers write smart contracts and other blockchain apps using Gnolang (Gno) without learning a language that’s exclusive to a single ecosystem."). - Set("MainContent", string(mainContent)). - Render(w, r, "generic.html", "funcs.html") - }) -} + rlmfullpath := "gno.land" + rlmpath + querystr := "" // XXX: "?gnoweb-alias=1" + parts := strings.Split(rlmpath, ":") + switch len(parts) { + case 1: // continue + case 2: // r/realm:querystr + rlmfullpath = "gno.land" + parts[0] + querystr = parts[1] + querystr + default: + panic("should not happen") + } + rlmname := strings.TrimPrefix(rlmfullpath, "gno.land/r/") + qpath := "vm/qrender" + data := []byte(fmt.Sprintf("%s\n%s", rlmfullpath, querystr)) + res, err := makeRequest(qpath, data) + if err != nil { + writeError(w, fmt.Errorf("gnoweb failed to query gnoland: %w", err)) + return + } -func handlerGor(app gotuna.App) http.Handler { - md := filepath.Join(flags.pagesDir, "GOR.md") - mainContent := osm.MustReadFile(md) + queryParts := strings.Split(querystr, "/") + pathLinks := []pathLink{} + for i, part := range queryParts { + pathLinks = append(pathLinks, pathLink{ + URL: "/r/" + rlmname + ":" + strings.Join(queryParts[:i+1], "/"), + Text: part, + }) + } - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - app.NewTemplatingEngine(). - Set("MainContent", string(mainContent)). - Set("Title", "Game of Realms Content For The Best Contributors "). - Set("Description", "Game of Realms is the first high-stakes competition held in two phases to find the best contributors to the Gno.land platform with a 133,700 ATOM prize pool."). - Render(w, r, "generic.html", "funcs.html") + tmpl := app.NewTemplatingEngine() + // XXX: extract title from realm's output + // XXX: extract description from realm's output + tmpl.Set("RealmName", rlmname) + tmpl.Set("RealmPath", rlmpath) + tmpl.Set("Query", querystr) + tmpl.Set("PathLinks", pathLinks) + tmpl.Set("Contents", string(res.Data)) + tmpl.Set("Flags", flags) + tmpl.Set("IsAlias", true) + tmpl.Render(w, r, "realm_render.html", "funcs.html") }) } func handlerFaucet(app gotuna.App) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { app.NewTemplatingEngine(). - Set("captchaSite", flags.captchaSite). - Set("faucetURL", flags.faucetURL). + Set("Flags", flags). Render(w, r, "faucet.html", "funcs.html") }) } @@ -192,12 +234,13 @@ func handlerStatusJSON(app gotuna.App) http.Handler { }) } -// XXX temporary. -func handlerRedirect(app gotuna.App) http.Handler { +func handlerRedirect(app gotuna.App, to string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/r/boards:gnolang/3", http.StatusFound) - app.NewTemplatingEngine(). - Render(w, r, "home.html", "funcs.html") + http.Redirect(w, r, to, http.StatusFound) + tmpl := app.NewTemplatingEngine() + tmpl.Set("To", to) + tmpl.Set("Flags", flags) + tmpl.Render(w, r, "redirect.html", "funcs.html") }) } @@ -232,10 +275,9 @@ func handlerRealmMain(app gotuna.App) http.Handler { tmpl := app.NewTemplatingEngine() tmpl.Set("FuncName", funcName) tmpl.Set("RealmPath", rlmpath) - tmpl.Set("Remote", flags.helpRemote) - tmpl.Set("ChainID", flags.helpChainID) tmpl.Set("DirPath", pathOf(rlmpath)) tmpl.Set("FunctionSignatures", fsigs) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "realm_help.html", "funcs.html") } else { // Ensure realm exists. TODO optimize. @@ -297,12 +339,14 @@ func handleRealmRender(app gotuna.App, w http.ResponseWriter, r *http.Request) { } // Render template. tmpl := app.NewTemplatingEngine() - + // XXX: extract title from realm's output + // XXX: extract description from realm's output tmpl.Set("RealmName", rlmname) tmpl.Set("RealmPath", rlmpath) tmpl.Set("Query", querystr) tmpl.Set("PathLinks", pathLinks) tmpl.Set("Contents", string(res.Data)) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "realm_render.html", "funcs.html") } @@ -345,6 +389,7 @@ func renderPackageFile(app gotuna.App, w http.ResponseWriter, r *http.Request, d tmpl.Set("DirURI", diruri) tmpl.Set("DirPath", pathOf(diruri)) tmpl.Set("Files", files) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "package_dir.html", "funcs.html") } else { // Request is for a file. @@ -362,6 +407,7 @@ func renderPackageFile(app gotuna.App, w http.ResponseWriter, r *http.Request, d tmpl.Set("DirPath", pathOf(diruri)) tmpl.Set("FileName", filename) tmpl.Set("FileContents", string(res.Data)) + tmpl.Set("Flags", flags) tmpl.Render(w, r, "package_file.html", "funcs.html") } } @@ -371,7 +417,7 @@ func makeRequest(qpath string, data []byte) (res *abci.ResponseQuery, err error) // Height: height, XXX // Prove: false, XXX } - remote := flags.remoteAddr + remote := flags.RemoteAddr cli := client.NewHTTP(remote, "/websocket") qres, err := cli.ABCIQueryWithOptions( qpath, data, opts2) @@ -432,11 +478,20 @@ func handleNotFound(app gotuna.App, path string, w http.ResponseWriter, r *http. app.NewTemplatingEngine(). Set("title", "Not found"). Set("path", path). + Set("Flags", flags). Render(w, r, "404.html", "funcs.html") } func writeError(w http.ResponseWriter, err error) { + // XXX: writeError should return an error page template. w.WriteHeader(500) + + fmt.Println("main", err.Error()) + + if details := errors.Unwrap(err); details != nil { + fmt.Println("details", details.Error()) + } + w.Write([]byte(err.Error())) } diff --git a/gno.land/cmd/gnoweb/main_test.go b/gno.land/cmd/gnoweb/main_test.go index 579e1bcd06b..61650563405 100644 --- a/gno.land/cmd/gnoweb/main_test.go +++ b/gno.land/cmd/gnoweb/main_test.go @@ -4,15 +4,21 @@ import ( "fmt" "net/http" "net/http/httptest" - "os" "strings" "testing" + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/tm2/pkg/log" "github.com/gotuna/gotuna/test/assert" ) func TestRoutes(t *testing.T) { - ok := http.StatusOK + const ( + ok = http.StatusOK + found = http.StatusFound + notFound = http.StatusNotFound + ) routes := []struct { route string status int @@ -32,14 +38,23 @@ func TestRoutes(t *testing.T) { {"/r/demo/deep/very/deep?help", ok, "exposed"}, {"/r/demo/deep/very/deep/", ok, "render.gno"}, {"/r/demo/deep/very/deep/render.gno", ok, "func Render("}, + {"/game-of-realms", ok, "/r/gnoland/pages:p/gor"}, + {"/gor", found, "/game-of-realms"}, + {"/blog", found, "/r/gnoland/blog"}, + {"/404-not-found", notFound, "/404-not-found"}, } - if wd, err := os.Getwd(); err == nil { - if strings.HasSuffix(wd, "cmd/gnoweb") { - os.Chdir("../..") - } - } else { - panic("os.Getwd() -> err: " + err.Error()) - } + + config, _ := integration.TestingNodeConfig(t, gnoland.MustGuessGnoRootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNopLogger(), config) + defer node.Stop() + + // set the `remoteAddr` of the client to the listening address of the + // node, which is randomly assigned. + flags.RemoteAddr = remoteAddr + flags.HelpChainID = "dev" + flags.CaptchaSite = "" + flags.ViewsDir = "../../cmd/gnoweb/views" + flags.WithAnalytics = false app := makeApp() for _, r := range routes { @@ -48,8 +63,64 @@ func TestRoutes(t *testing.T) { response := httptest.NewRecorder() app.Router.ServeHTTP(response, request) assert.Equal(t, r.status, response.Code) - assert.Equal(t, strings.Contains(response.Body.String(), r.substring), true) - println(response.Body.String()) + assert.Contains(t, response.Body.String(), r.substring) + // println(response.Body.String()) }) } } + +func TestAnalytics(t *testing.T) { + routes := []string{ + // special realms + "/", // home + "/about", + "/start", + + // redirects + "/game-of-realms", + "/getting-started", + "/blog", + "/boards", + + // realm, source, help page + "/r/gnoland/blog", + "/r/gnoland/blog/admin.gno", + "/r/demo/users:administrator", + "/r/gnoland/blog?help", + + // special pages + "/404-not-found", + } + + config, _ := integration.TestingNodeConfig(t, gnoland.MustGuessGnoRootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNopLogger(), config) + defer node.Stop() + + flags.ViewsDir = "../../cmd/gnoweb/views" + t.Run("with", func(t *testing.T) { + for _, route := range routes { + t.Run(route, func(t *testing.T) { + flags.RemoteAddr = remoteAddr + flags.WithAnalytics = true + app := makeApp() + request := httptest.NewRequest(http.MethodGet, route, nil) + response := httptest.NewRecorder() + app.Router.ServeHTTP(response, request) + assert.Contains(t, response.Body.String(), "sa.gno.services") + }) + } + }) + t.Run("without", func(t *testing.T) { + for _, route := range routes { + t.Run(route, func(t *testing.T) { + flags.RemoteAddr = remoteAddr + flags.WithAnalytics = false + app := makeApp() + request := httptest.NewRequest(http.MethodGet, route, nil) + response := httptest.NewRecorder() + app.Router.ServeHTTP(response, request) + assert.Equal(t, strings.Contains(response.Body.String(), "sa.gno.services"), false) + }) + } + }) +} diff --git a/gno.land/cmd/gnoweb/pages/GOR.md b/gno.land/cmd/gnoweb/pages/GOR.md deleted file mode 100644 index 95a155319fb..00000000000 --- a/gno.land/cmd/gnoweb/pages/GOR.md +++ /dev/null @@ -1,30 +0,0 @@ -# Game of Realms - -The first high-stakes contest will see participants compete for the tiered membership to co-own the Gno.land blockchain. -A series of complex technical and non-technical tasks will challenge contributors to create innovative patterns that push the chain to new limits. -Start building the foundation for tomorrow through key smart contracts and other contributions that change our understanding of the world. - -You can start participating in the co-creation of the Game of Realms now by adding your contributions on the dedicated [GitHub page](https://github.com/gnolang/gno/issues/357) to help us formulate the challenges. We want to release the final challenges from your contributions. - -## Register Now - - -
-
- -
-
- - -
- -
- - -
- - -
-
-
- diff --git a/gno.land/cmd/gnoweb/pages/HOME.md b/gno.land/cmd/gnoweb/pages/HOME.md deleted file mode 100644 index 0f3bfaec685..00000000000 --- a/gno.land/cmd/gnoweb/pages/HOME.md +++ /dev/null @@ -1,50 +0,0 @@ -# Welcome to **Gno.land** - -- [About Gno.land](/about) -- [Blogs](/r/gnoland/blog) -- [Install `gnokey`](https://github.com/gnolang/gno/tree/master/gno.land/cmd/gnokey) -- [Acquire testnet tokens](/faucet) -- [Game of Realms](/game-of-realms) - An open worldwide competition for developers to build the best Gnolang smart-contracts. - -# Explore new packages. - -- r/gnoland - - [/r/gnoland/blog](/r/gnoland/blog) - - [/r/gnoland/faucet](/r/gnoland/faucet) -- r/system - - [/r/system/names](/r/system/names) - - [/r/system/rewards](/r/system/rewards) - - [/r/system/validators](/r/system/validators) -- r/demo - - [/r/demo/banktest](/r/demo/banktest) - - [/r/demo/boards](/r/demo/boards) - - [/r/demo/foo20](/r/demo/foo20) - - [/r/demo/nft](/r/demo/nft) - - [/r/demo/types](/r/demo/types) - - [/r/demo/users](/r/demo/users) - - [/r/demo/groups](/r/demo/groups) -- p/demo - - [/p/demo/avl](/p/demo/avl) - - [/p/demo/blog](/p/demo/blog) - - [/p/demo/flow](/p/demo/flow) - - [/p/demo/gnode](/p/demo/gnode) - - [/p/demo/grc/exts](/p/demo/grc/exts) - - [/p/demo/grc/grc20](/p/demo/grc/grc20) - - [/p/demo/grc/grc721](/p/demo/grc/grc721) - -# Other Testnets - -- **[staging.gno.land](https://staging.gno.land) (wiped every commit to master)** -- _[test3.gno.land](https://test3.gno.land) (latest)_ -- _[test2.gno.land](https://test2.gno.land) (archive)_ -- _[test1.gno.land](https://test1.gno.land) (archive)_ - -**This is a testnet.** -Package names are not guaranteed to be available for production. - -# Social - -Check out our [community projects](https://github.com/gnolang/awesome-gno). - -Official channel: [Discord](https://discord.gg/S8nKUqwkPn)
-Other channels: [Telegram](https://t.me/gnoland) [Twitter](https://twitter.com/_gnoland) [Youtube](https://www.youtube.com/@_gnoland) diff --git a/gno.land/cmd/gnoweb/views/404.html b/gno.land/cmd/gnoweb/views/404.html index c51543ca1cd..fee4fff8689 100644 --- a/gno.land/cmd/gnoweb/views/404.html +++ b/gno.land/cmd/gnoweb/views/404.html @@ -2,8 +2,7 @@ - - + {{ template "html_head" . }} 404 - Not Found @@ -13,6 +12,7 @@

{{.Data.path}}

+ {{ template "analytics" .}} {{- end -}} diff --git a/gno.land/cmd/gnoweb/views/faucet.html b/gno.land/cmd/gnoweb/views/faucet.html index 84bcc6f34e5..85d3d6780c5 100644 --- a/gno.land/cmd/gnoweb/views/faucet.html +++ b/gno.land/cmd/gnoweb/views/faucet.html @@ -2,8 +2,8 @@ + {{ template "html_head" . }} Gno.land - {{ template "html_head" }}
diff --git a/gno.land/cmd/gnoweb/views/funcs.html b/gno.land/cmd/gnoweb/views/funcs.html index 8ac2fe6328b..c8d643ef655 100644 --- a/gno.land/cmd/gnoweb/views/funcs.html +++ b/gno.land/cmd/gnoweb/views/funcs.html @@ -1,4 +1,4 @@ -{{ define "header_buttons" }} +{{- define "header_buttons" -}}
-{{ end }} {{ define "html_head" }} +{{- end -}} + +{{- define "html_head" -}} + +{{if .Data.Description}}{{end}} -{{ end }} {{ define "header_logo" }} +{{- end -}} + +{{- define "header_logo" -}} -{{ end }} {{ define "footer" }} +{{- end -}} + +{{- define "footer" -}}
Gno.land
-{{ end }} {{ define "js" }} +{{- end -}} + +{{- define "js" -}} + -{{ end }} {{ define "subscribe" }} +{{ template "analytics" .}} +{{- end -}} + +{{- define "analytics" -}} +{{- if .Data.Flags.WithAnalytics -}} + + + +{{- end -}} +{{- end -}} + +{{- define "subscribe" -}}
-{{ end }} +{{- end -}} diff --git a/gno.land/cmd/gnoweb/views/generic.html b/gno.land/cmd/gnoweb/views/generic.html index 117ea9d59fe..e671625e26a 100644 --- a/gno.land/cmd/gnoweb/views/generic.html +++ b/gno.land/cmd/gnoweb/views/generic.html @@ -3,8 +3,7 @@ Gno.land - {{ .Data.Title }} - - {{ template "html_head" }} + {{ template "html_head" . }}
@@ -16,7 +15,7 @@
{{ template "footer" }}
- {{ template "js" }} + {{ template "js" .}} {{- end -}} diff --git a/gno.land/cmd/gnoweb/views/home.html b/gno.land/cmd/gnoweb/views/home.html deleted file mode 100644 index a2bf78adb96..00000000000 --- a/gno.land/cmd/gnoweb/views/home.html +++ /dev/null @@ -1,21 +0,0 @@ -{{- define "app" -}} - - - - Gno.land - {{ .Data.Title }} - - {{ template "html_head" }} - - -
- -
-
{{ .Data.HomeContent }}
-
-
{{ template "subscribe" }}
- {{ template "footer" }} -
- {{ template "js" }} - - -{{- end -}} diff --git a/gno.land/cmd/gnoweb/views/package_dir.html b/gno.land/cmd/gnoweb/views/package_dir.html index 6b0bf5cfd48..efaf4d7ad0c 100644 --- a/gno.land/cmd/gnoweb/views/package_dir.html +++ b/gno.land/cmd/gnoweb/views/package_dir.html @@ -2,8 +2,8 @@ - Gno.land - {{ template "html_head" }} + {{ template "html_head" . }} + Gno.land - {{.Data.DirPath}}
@@ -12,13 +12,14 @@ {{ .Data.DirPath }}/*
{{ template "dir_contents" . }}
- {{ template "footer" }} - {{ template "js" }} + {{ template "js" . }} -{{- end -}} {{ define "dir_contents" }} +{{- end -}} + +{{- define "dir_contents" -}}
{{ $dirPath := .Data.DirPath }}
    @@ -29,4 +30,4 @@ {{ end }}
-{{ end }} +{{- end -}} diff --git a/gno.land/cmd/gnoweb/views/package_file.html b/gno.land/cmd/gnoweb/views/package_file.html index 7068854d16c..71aa8b68452 100644 --- a/gno.land/cmd/gnoweb/views/package_file.html +++ b/gno.land/cmd/gnoweb/views/package_file.html @@ -2,8 +2,8 @@ - Gno.land - {{ template "html_head" }} + {{ template "html_head" . }} + Gno.land - {{.Data.DirPath}}/{{.Data.FileName}}
@@ -18,7 +18,7 @@ {{ template "footer" }}
- {{ template "js" }} + {{ template "js" .}} - + {{ template "js" . }} -{{- end -}} +{{ define "func_specs" }} +{{- end -}} + +{{- define "func_specs" -}}
{{ $funcName := .Data.FuncName }} {{ $found := false }} {{ if eq $funcName "" }} {{ range .Data.FunctionSignatures }} {{ template "func_spec" . }} {{ end }} {{ else }} {{ range .Data.FunctionSignatures }} {{ if eq .FuncName $funcName }} {{ $found = true }} {{ template "func_spec" . }} {{ end }} {{ end }} {{ if not $found }} {{ $funcName }} not found. {{ end }} {{ end }}
-{{ end }} {{ define "func_spec" }} +{{- end -}} + +{{- define "func_spec" -}}
@@ -67,7 +69,9 @@
-{{ end }} {{ define "func_param" }} +{{- end -}} + +{{- define "func_param" -}} {{ .Name }} @@ -75,7 +79,9 @@ {{ .Type }} -{{ end }} {{ define "func_result" }} +{{- end -}} + +{{- define "func_result" -}} {{ .Name }} {{ .Type }} diff --git a/gno.land/cmd/gnoweb/views/realm_render.html b/gno.land/cmd/gnoweb/views/realm_render.html index 8a2c35adeca..6337d77aafa 100644 --- a/gno.land/cmd/gnoweb/views/realm_render.html +++ b/gno.land/cmd/gnoweb/views/realm_render.html @@ -2,8 +2,8 @@ - Gno.land - {{ template "html_head" }} + {{ template "html_head" . }} + Gno.land - {{.Data.RealmName}}
@@ -26,10 +26,7 @@
{{ template "footer" }} - {{ template "js" }} - - - + {{ template "js" .}} {{- end -}} diff --git a/gno.land/cmd/gnoweb/views/redirect.html b/gno.land/cmd/gnoweb/views/redirect.html new file mode 100644 index 00000000000..6fe43a7138b --- /dev/null +++ b/gno.land/cmd/gnoweb/views/redirect.html @@ -0,0 +1,16 @@ +{{- define "app" -}} + + + + + + + + Redirecting to {{.Data.To}} + + + {{.Data.To}} + {{ template "analytics" .}} + + +{{- end -}} diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index d6c9ed40e4a..e1f3aae98dc 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -1,16 +1,17 @@ package gnoland import ( + "errors" "fmt" "os" "os/exec" "path/filepath" + "runtime" "strings" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/amino" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" - "github.com/gnolang/gno/tm2/pkg/crypto" dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/sdk" @@ -36,7 +37,7 @@ func NewAppOptions() *AppOptions { return &AppOptions{ Logger: log.NewNopLogger(), DB: dbm.NewMemDB(), - GnoRootDir: GuessGnoRootDir(), + GnoRootDir: MustGuessGnoRootDir(), } } @@ -73,6 +74,8 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // Construct keepers. acctKpr := auth.NewAccountKeeper(mainKey, ProtoGnoAccount) bankKpr := bank.NewBankKeeper(acctKpr) + + // XXX: Embed this ? stdlibsDir := filepath.Join(cfg.GnoRootDir, "gnovm", "stdlibs") vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) @@ -143,10 +146,9 @@ func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank genState := req.AppState.(GnoGenesisState) // Parse and set genesis state balances. for _, bal := range genState.Balances { - addr, coins := parseBalance(bal) - acc := acctKpr.NewAccountWithAddress(ctx, addr) + acc := acctKpr.NewAccountWithAddress(ctx, bal.Address) acctKpr.SetAccount(ctx, acc) - err := bankKpr.SetCoins(ctx, addr, coins) + err := bankKpr.SetCoins(ctx, bal.Address, bal.Amount) if err != nil { panic(err) } @@ -173,22 +175,6 @@ func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank } } -func parseBalance(bal string) (crypto.Address, std.Coins) { - parts := strings.Split(bal, "=") - if len(parts) != 2 { - panic(fmt.Sprintf("invalid balance string %s", bal)) - } - addr, err := crypto.AddressFromBech32(parts[0]) - if err != nil { - panic(fmt.Sprintf("invalid balance addr %s (%v)", bal, err)) - } - coins, err := std.ParseCoins(parts[1]) - if err != nil { - panic(fmt.Sprintf("invalid balance coins %s (%v)", bal, err)) - } - return addr, coins -} - // XXX not used yet. func EndBlocker(vmk vm.VMKeeperI) func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { @@ -196,24 +182,44 @@ func EndBlocker(vmk vm.VMKeeperI) func(ctx sdk.Context, req abci.RequestEndBlock } } -func GuessGnoRootDir() string { - var rootdir string +// XXX: all the method bellow should be removed in favor of +// https://github.com/gnolang/gno/pull/1233 +func MustGuessGnoRootDir() string { + root, err := GuessGnoRootDir() + if err != nil { + panic(err) + } + + return root +} +func GuessGnoRootDir() (string, error) { // First try to get the root directory from the GNOROOT environment variable. - if rootdir = os.Getenv("GNOROOT"); rootdir != "" { - return filepath.Clean(rootdir) + if rootdir := os.Getenv("GNOROOT"); rootdir != "" { + return filepath.Clean(rootdir), nil } + // Try to guess GNOROOT using the nearest go.mod. if gobin, err := exec.LookPath("go"); err == nil { // If GNOROOT is not set, try to guess the root directory using the `go list` command. cmd := exec.Command(gobin, "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno") out, err := cmd.CombinedOutput() - if err != nil { - panic(fmt.Errorf("invalid gno directory %q: %w", rootdir, err)) + if err == nil { + return strings.TrimSpace(string(out)), nil } + } - return strings.TrimSpace(string(out)) + // Try to guess GNOROOT using caller stack. + if _, filename, _, ok := runtime.Caller(1); ok && filepath.IsAbs(filename) { + if currentDir := filepath.Dir(filename); currentDir != "" { + // Gno root directory relative from `app.go` path: + // gno/ .. /gno.land/ .. /pkg/ .. /gnoland/app.go + rootdir, err := filepath.Abs(filepath.Join(currentDir, "..", "..", "..")) + if err == nil { + return rootdir, nil + } + } } - panic("no go binary available, unable to determine gno root-dir path") + return "", errors.New("unable to guess gno's root-directory") } diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go new file mode 100644 index 00000000000..e809103469d --- /dev/null +++ b/gno.land/pkg/gnoland/genesis.go @@ -0,0 +1,126 @@ +package gnoland + +import ( + "errors" + "fmt" + "strings" + + vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/tm2/pkg/amino" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + osm "github.com/gnolang/gno/tm2/pkg/os" + "github.com/gnolang/gno/tm2/pkg/std" +) + +// LoadGenesisBalancesFile loads genesis balances from the provided file path. +func LoadGenesisBalancesFile(path string) ([]Balance, error) { + // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot + content := osm.MustReadFile(path) + lines := strings.Split(string(content), "\n") + + balances := make([]Balance, 0, len(lines)) + for _, line := range lines { + line = strings.TrimSpace(line) + + // remove comments. + line = strings.Split(line, "#")[0] + line = strings.TrimSpace(line) + + // skip empty lines. + if line == "" { + continue + } + + parts := strings.Split(line, "=") //
= + if len(parts) != 2 { + return nil, errors.New("invalid genesis_balance line: " + line) + } + + addr, err := crypto.AddressFromBech32(parts[0]) + if err != nil { + return nil, fmt.Errorf("invalid balance addr %s: %w", parts[0], err) + } + + coins, err := std.ParseCoins(parts[1]) + if err != nil { + return nil, fmt.Errorf("invalid balance coins %s: %w", parts[1], err) + } + + balances = append(balances, Balance{ + Address: addr, + Amount: coins, + }) + } + + return balances, nil +} + +// LoadGenesisTxsFile loads genesis transactions from the provided file path. +// XXX: Improve the way we generate and load this file +func LoadGenesisTxsFile(path string, chainID string, genesisRemote string) ([]std.Tx, error) { + txs := []std.Tx{} + txsBz := osm.MustReadFile(path) + txsLines := strings.Split(string(txsBz), "\n") + for _, txLine := range txsLines { + if txLine == "" { + continue // Skip empty line. + } + + // Patch the TX. + txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID) + txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote) + + var tx std.Tx + if err := amino.UnmarshalJSON([]byte(txLine), &tx); err != nil { + return nil, fmt.Errorf("unable to Unmarshall txs file: %w", err) + } + + txs = append(txs, tx) + } + + return txs, nil +} + +// LoadPackagesFromDir loads gno packages from a directory. +// It creates and returns a list of transactions based on these packages. +func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee, deposit std.Coins) ([]std.Tx, error) { + // list all packages from target path + pkgs, err := gnomod.ListPkgs(dir) + if err != nil { + return nil, fmt.Errorf("listing gno packages: %w", err) + } + + // Sort packages by dependencies. + sortedPkgs, err := pkgs.Sort() + if err != nil { + return nil, fmt.Errorf("sorting packages: %w", err) + } + + // Filter out draft packages. + nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() + txs := []std.Tx{} + for _, pkg := range nonDraftPkgs { + // Open files in directory as MemPackage. + memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) + + // Create transaction + tx := std.Tx{ + Fee: fee, + Msgs: []std.Msg{ + vmm.MsgAddPackage{ + Creator: creator, + Package: memPkg, + Deposit: deposit, + }, + }, + } + + tx.Signatures = make([]std.Signature, len(tx.GetSigners())) + txs = append(txs, tx) + } + + return txs, nil +} diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go new file mode 100644 index 00000000000..0edef304281 --- /dev/null +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -0,0 +1,147 @@ +package gnoland + +import ( + "fmt" + "time" + + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" + "github.com/gnolang/gno/tm2/pkg/bft/node" + "github.com/gnolang/gno/tm2/pkg/bft/proxy" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" + "github.com/gnolang/gno/tm2/pkg/db" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/p2p" + "github.com/gnolang/gno/tm2/pkg/std" +) + +type InMemoryNodeConfig struct { + PrivValidator bft.PrivValidator // identity of the validator + Genesis *bft.GenesisDoc + TMConfig *tmcfg.Config + SkipFailingGenesisTxs bool + GenesisMaxVMCycles int64 +} + +// NewMockedPrivValidator generate a new key +func NewMockedPrivValidator() bft.PrivValidator { + return bft.NewMockPVWithParams(ed25519.GenPrivKey(), false, false) +} + +// NewInMemoryNodeConfig creates a default configuration for an in-memory node. +func NewDefaultGenesisConfig(pk crypto.PubKey, chainid string) *bft.GenesisDoc { + return &bft.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: chainid, + ConsensusParams: abci.ConsensusParams{ + Block: &abci.BlockParams{ + MaxTxBytes: 1_000_000, // 1MB, + MaxDataBytes: 2_000_000, // 2MB, + MaxGas: 10_0000_000, // 10M gas + TimeIotaMS: 100, // 100ms + }, + }, + AppState: &GnoGenesisState{ + Balances: []Balance{}, + Txs: []std.Tx{}, + }, + } +} + +func NewDefaultTMConfig(rootdir string) *tmcfg.Config { + return tmcfg.DefaultConfig().SetRootDir(rootdir) +} + +// NewInMemoryNodeConfig creates a default configuration for an in-memory node. +func NewDefaultInMemoryNodeConfig(rootdir string) *InMemoryNodeConfig { + tm := NewDefaultTMConfig(rootdir) + + // Create Mocked Identity + pv := NewMockedPrivValidator() + genesis := NewDefaultGenesisConfig(pv.GetPubKey(), tm.ChainID()) + + // Add self as validator + self := pv.GetPubKey() + genesis.Validators = []bft.GenesisValidator{ + { + Address: self.Address(), + PubKey: self, + Power: 10, + Name: "self", + }, + } + + return &InMemoryNodeConfig{ + PrivValidator: pv, + TMConfig: tm, + Genesis: genesis, + GenesisMaxVMCycles: 10_000_000, + } +} + +func (cfg *InMemoryNodeConfig) validate() error { + if cfg.PrivValidator == nil { + return fmt.Errorf("`PrivValidator` is required but not provided") + } + + if cfg.TMConfig == nil { + return fmt.Errorf("`TMConfig` is required but not provided") + } + + if cfg.TMConfig.RootDir == "" { + return fmt.Errorf("`TMConfig.RootDir` is required to locate `stdlibs` directory") + } + + return nil +} + +// NewInMemoryNode creates an in-memory gnoland node. In this mode, the node does not +// persist any data and uses an in-memory database. The `InMemoryNodeConfig.TMConfig.RootDir` +// should point to the correct gno repository to load the stdlibs. +func NewInMemoryNode(logger log.Logger, cfg *InMemoryNodeConfig) (*node.Node, error) { + if err := cfg.validate(); err != nil { + return nil, fmt.Errorf("validate config error: %w", err) + } + + // Initialize the application with the provided options + gnoApp, err := NewAppWithOptions(&AppOptions{ + Logger: logger, + GnoRootDir: cfg.TMConfig.RootDir, + SkipFailingGenesisTxs: cfg.SkipFailingGenesisTxs, + MaxCycles: cfg.GenesisMaxVMCycles, + DB: db.NewMemDB(), + }) + if err != nil { + return nil, fmt.Errorf("error initializing new app: %w", err) + } + + cfg.TMConfig.LocalApp = gnoApp + + // Setup app client creator + appClientCreator := proxy.DefaultClientCreator( + cfg.TMConfig.LocalApp, + cfg.TMConfig.ProxyApp, + cfg.TMConfig.ABCI, + cfg.TMConfig.DBDir(), + ) + + // Create genesis factory + genProvider := func() (*bft.GenesisDoc, error) { return cfg.Genesis, nil } + + dbProvider := func(*node.DBContext) (db.DB, error) { return db.NewMemDB(), nil } + + // generate p2p node identity + // XXX: do we need to configur + nodekey := &p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} + + // Create and return the in-memory node instance + return node.NewNode(cfg.TMConfig, + cfg.PrivValidator, nodekey, + appClientCreator, + genProvider, + dbProvider, + logger, + ) +} diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go index 1c762366ae9..5d68064c9c5 100644 --- a/gno.land/pkg/gnoland/types.go +++ b/gno.land/pkg/gnoland/types.go @@ -1,9 +1,20 @@ package gnoland import ( + "errors" + "fmt" + "strings" + + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" ) +var ( + ErrBalanceEmptyAddress = errors.New("balance address is empty") + ErrBalanceEmptyAmount = errors.New("balance amount is empty") +) + type GnoAccount struct { std.BaseAccount } @@ -13,6 +24,56 @@ func ProtoGnoAccount() std.Account { } type GnoGenesisState struct { - Balances []string `json:"balances"` - Txs []std.Tx `json:"txs"` + Balances []Balance `json:"balances"` + Txs []std.Tx `json:"txs"` +} + +type Balance struct { + Address bft.Address + Amount std.Coins +} + +func (b *Balance) Verify() error { + if b.Address.IsZero() { + return ErrBalanceEmptyAddress + } + + if b.Amount.Len() == 0 { + return ErrBalanceEmptyAmount + } + + return nil +} + +func (b *Balance) Parse(entry string) error { + parts := strings.Split(strings.TrimSpace(entry), "=") //
= + if len(parts) != 2 { + return fmt.Errorf("malformed entry: %q", entry) + } + + var err error + + b.Address, err = crypto.AddressFromBech32(parts[0]) + if err != nil { + return fmt.Errorf("invalid address %q: %w", parts[0], err) + } + + b.Amount, err = std.ParseCoins(parts[1]) + if err != nil { + return fmt.Errorf("invalid amount %q: %w", parts[1], err) + } + + return nil +} + +func (b *Balance) UnmarshalAmino(rep string) error { + return b.Parse(rep) +} + +func (b Balance) MarshalAmino() (string, error) { + return b.String(), nil +} + +func (b Balance) String() string { + return fmt.Sprintf("%s=%s", b.Address.String(), b.Amount.String()) } diff --git a/gno.land/pkg/gnoland/types_test.go b/gno.land/pkg/gnoland/types_test.go new file mode 100644 index 00000000000..97222d0cdfd --- /dev/null +++ b/gno.land/pkg/gnoland/types_test.go @@ -0,0 +1,98 @@ +package gnoland + +import ( + "fmt" + "testing" + + "github.com/gnolang/gno/tm2/pkg/amino" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/jaekwon/testify/assert" + "github.com/jaekwon/testify/require" +) + +func TestBalance_Verify(t *testing.T) { + validAddress := crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + emptyAmount := std.Coins{} + nonEmptyAmount := std.NewCoins(std.NewCoin("test", 100)) + + tests := []struct { + name string + balance Balance + expectErr bool + }{ + {"empty amount", Balance{Address: validAddress, Amount: emptyAmount}, true}, + {"empty address", Balance{Address: bft.Address{}, Amount: nonEmptyAmount}, true}, + {"valid balance", Balance{Address: validAddress, Amount: nonEmptyAmount}, false}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.balance.Verify() + if tc.expectErr { + assert.Error(t, err, fmt.Sprintf("TestVerifyBalance: %s", tc.name)) + } else { + assert.NoError(t, err, fmt.Sprintf("TestVerifyBalance: %s", tc.name)) + } + }) + } +} + +func TestBalance_Parse(t *testing.T) { + validAddress := crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + validBalance := Balance{Address: validAddress, Amount: std.NewCoins(std.NewCoin("test", 100))} + + tests := []struct { + name string + entry string + expected Balance + expectErr bool + }{ + {"valid entry", "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5=100test", validBalance, false}, + {"invalid address", "invalid=100test", Balance{}, true}, + {"incomplete entry", "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", Balance{}, true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + balance := Balance{} + err := balance.Parse(tc.entry) + if tc.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expected, balance) + } + }) + } +} + +func TestBalance_AminoUnmarshalJSON(t *testing.T) { + expected := Balance{ + Address: crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + Amount: std.MustParseCoins("100ugnot"), + } + value := fmt.Sprintf("[%q]", expected.String()) + + var balances []Balance + err := amino.UnmarshalJSON([]byte(value), &balances) + require.NoError(t, err) + require.Len(t, balances, 1, "there should be one balance after unmarshaling") + + balance := balances[0] + require.Equal(t, expected.Address, balance.Address) + require.True(t, expected.Amount.IsEqual(balance.Amount)) +} + +func TestBalance_AminoMarshalJSON(t *testing.T) { + expected := Balance{ + Address: crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + Amount: std.MustParseCoins("100ugnot"), + } + expectedJSON := fmt.Sprintf("[%q]", expected.String()) + + balancesJSON, err := amino.MarshalJSON([]Balance{expected}) + require.NoError(t, err) + require.JSONEq(t, expectedJSON, string(balancesJSON)) +} diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go deleted file mode 100644 index c4fee341bfc..00000000000 --- a/gno.land/pkg/integration/gnoland.go +++ /dev/null @@ -1,336 +0,0 @@ -package integration - -import ( - "flag" - "fmt" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/gnolang/gno/gno.land/pkg/gnoland" - vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" - "github.com/gnolang/gno/tm2/pkg/amino" - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" - "github.com/gnolang/gno/tm2/pkg/bft/config" - "github.com/gnolang/gno/tm2/pkg/bft/node" - "github.com/gnolang/gno/tm2/pkg/bft/privval" - bft "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/db" - "github.com/gnolang/gno/tm2/pkg/log" - osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/std" - "github.com/rogpeppe/go-internal/testscript" -) - -type IntegrationConfig struct { - SkipFailingGenesisTxs bool - SkipStart bool - GenesisBalancesFile string - GenesisTxsFile string - ChainID string - GenesisRemote string - RootDir string - GenesisMaxVMCycles int64 - Config string -} - -// NOTE: This is a copy of gnoland actual flags. -// XXX: A lot this make no sense for integration. -func (c *IntegrationConfig) RegisterFlags(fs *flag.FlagSet) { - fs.BoolVar( - &c.SkipFailingGenesisTxs, - "skip-failing-genesis-txs", - false, - "don't panic when replaying invalid genesis txs", - ) - fs.BoolVar( - &c.SkipStart, - "skip-start", - false, - "quit after initialization, don't start the node", - ) - - fs.StringVar( - &c.GenesisBalancesFile, - "genesis-balances-file", - "./genesis/genesis_balances.txt", - "initial distribution file", - ) - - fs.StringVar( - &c.GenesisTxsFile, - "genesis-txs-file", - "./genesis/genesis_txs.txt", - "initial txs to replay", - ) - - fs.StringVar( - &c.ChainID, - "chainid", - "dev", - "the ID of the chain", - ) - - fs.StringVar( - &c.RootDir, - "root-dir", - "testdir", - "directory for config and data", - ) - - fs.StringVar( - &c.GenesisRemote, - "genesis-remote", - "localhost:26657", - "replacement for '%%REMOTE%%' in genesis", - ) - - fs.Int64Var( - &c.GenesisMaxVMCycles, - "genesis-max-vm-cycles", - 10_000_000, - "set maximum allowed vm cycles per operation. Zero means no limit.", - ) -} - -func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { - t.Helper() - - // Setup start config. - icfg := &IntegrationConfig{} - { - fs := flag.NewFlagSet("start", flag.ExitOnError) - icfg.RegisterFlags(fs) - - // Override default value for flags. - fs.VisitAll(func(f *flag.Flag) { - switch f.Name { - case "root-dir": - f.DefValue = gnoDataDir - case "chainid": - f.DefValue = "tendermint_test" - case "genesis-balances-file": - f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_balances.txt") - case "genesis-txs-file": - f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_txs.txt") - default: - return - } - - f.Value.Set(f.DefValue) - }) - - if err := fs.Parse(args); err != nil { - return nil, fmt.Errorf("unable to parse flags: %w", err) - } - } - - // Setup testing config. - cfg := config.TestConfig().SetRootDir(gnoDataDir) - { - cfg.EnsureDirs() - cfg.Consensus.CreateEmptyBlocks = true - cfg.Consensus.CreateEmptyBlocksInterval = time.Duration(0) - cfg.RPC.ListenAddress = "tcp://127.0.0.1:0" - cfg.P2P.ListenAddress = "tcp://127.0.0.1:0" - } - - // Prepare genesis. - if err := setupTestingGenesis(gnoDataDir, cfg, icfg, gnoRootDir); err != nil { - return nil, err - } - - // Create application and node. - return createAppAndNode(cfg, logger, gnoRootDir, icfg) -} - -func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *IntegrationConfig, gnoRootDir string) error { - newPrivValKey := cfg.PrivValidatorKeyFile() - newPrivValState := cfg.PrivValidatorStateFile() - priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) - - genesisFilePath := filepath.Join(gnoDataDir, cfg.Genesis) - genesisDirPath := filepath.Dir(genesisFilePath) - if err := osm.EnsureDir(genesisDirPath, 0o700); err != nil { - return fmt.Errorf("unable to ensure directory %q: %w", genesisDirPath, err) - } - - genesisTxs := loadGenesisTxs(icfg.GenesisTxsFile, icfg.ChainID, icfg.GenesisRemote) - pvPub := priv.GetPubKey() - - gen := &bft.GenesisDoc{ - GenesisTime: time.Now(), - ChainID: icfg.ChainID, - ConsensusParams: abci.ConsensusParams{ - Block: &abci.BlockParams{ - // TODO: update limits. - MaxTxBytes: 1000000, // 1MB, - MaxDataBytes: 2000000, // 2MB, - MaxGas: 10000000, // 10M gas - TimeIotaMS: 100, // 100ms - }, - }, - Validators: []bft.GenesisValidator{ - { - Address: pvPub.Address(), - PubKey: pvPub, - Power: 10, - Name: "testvalidator", - }, - }, - } - - // Load distribution. - balances := loadGenesisBalances(icfg.GenesisBalancesFile) - - // Load initial packages from examples. - // XXX: We should be able to config this. - test1 := crypto.MustAddressFromString(test1Addr) - txs := []std.Tx{} - - // List initial packages to load from examples. - // println(filepath.Join(gnoRootDir, "examples")) - pkgs, err := gnomod.ListPkgs(filepath.Join(gnoRootDir, "examples")) - if err != nil { - return fmt.Errorf("listing gno packages: %w", err) - } - - // Sort packages by dependencies. - sortedPkgs, err := pkgs.Sort() - if err != nil { - return fmt.Errorf("sorting packages: %w", err) - } - - // Filter out draft packages. - nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() - - for _, pkg := range nonDraftPkgs { - // Open files in directory as MemPackage. - memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) - - var tx std.Tx - tx.Msgs = []std.Msg{ - vmm.MsgAddPackage{ - Creator: test1, - Package: memPkg, - Deposit: nil, - }, - } - - // XXX: Add fee flag ? - // Or maybe reduce fee to the minimum ? - tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) - tx.Signatures = make([]std.Signature, len(tx.GetSigners())) - txs = append(txs, tx) - } - - // Load genesis txs from file. - txs = append(txs, genesisTxs...) - - // Construct genesis AppState. - gen.AppState = gnoland.GnoGenesisState{ - Balances: balances, - Txs: txs, - } - - writeGenesisFile(gen, genesisFilePath) - - return nil -} - -func createAppAndNode(cfg *config.Config, logger log.Logger, gnoRootDir string, icfg *IntegrationConfig) (*node.Node, error) { - gnoApp, err := gnoland.NewAppWithOptions(&gnoland.AppOptions{ - Logger: logger, - GnoRootDir: gnoRootDir, - SkipFailingGenesisTxs: icfg.SkipFailingGenesisTxs, - MaxCycles: icfg.GenesisMaxVMCycles, - DB: db.NewMemDB(), - }) - if err != nil { - return nil, fmt.Errorf("error in creating new app: %w", err) - } - - cfg.LocalApp = gnoApp - node, err := node.DefaultNewNode(cfg, logger) - if err != nil { - return nil, fmt.Errorf("error in creating node: %w", err) - } - - return node, node.Start() -} - -func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { - if err != nil { - fmt.Fprintf(ts.Stderr(), "%q error: %v\n", cmd, err) - if !neg { - ts.Fatalf("unexpected %q command failure: %s", cmd, err) - } - } else { - if neg { - ts.Fatalf("unexpected %s command success", cmd) - } - } -} - -func loadGenesisTxs( - path string, - chainID string, - genesisRemote string, -) []std.Tx { - txs := []std.Tx{} - txsBz := osm.MustReadFile(path) - txsLines := strings.Split(string(txsBz), "\n") - for _, txLine := range txsLines { - if txLine == "" { - continue // Skip empty line. - } - - // Patch the TX. - txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID) - txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote) - - var tx std.Tx - amino.MustUnmarshalJSON([]byte(txLine), &tx) - txs = append(txs, tx) - } - - return txs -} - -func loadGenesisBalances(path string) []string { - // Each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot. - balances := []string{} - content := osm.MustReadFile(path) - lines := strings.Split(string(content), "\n") - for _, line := range lines { - line = strings.TrimSpace(line) - - // Remove comments. - line = strings.Split(line, "#")[0] - line = strings.TrimSpace(line) - - // Skip empty lines. - if line == "" { - continue - } - - parts := strings.Split(line, "=") - if len(parts) != 2 { - panic("invalid genesis_balance line: " + line) - } - - balances = append(balances, line) - } - return balances -} - -func writeGenesisFile(gen *bft.GenesisDoc, filePath string) { - err := gen.SaveAs(filePath) - if err != nil { - panic(err) - } -} diff --git a/gno.land/pkg/integration/integration_test.go b/gno.land/pkg/integration/integration_test.go index 3c22a190d64..f88707f2b90 100644 --- a/gno.land/pkg/integration/integration_test.go +++ b/gno.land/pkg/integration/integration_test.go @@ -2,10 +2,10 @@ package integration import ( "testing" - - "github.com/rogpeppe/go-internal/testscript" ) func TestTestdata(t *testing.T) { - testscript.Run(t, SetupGnolandTestScript(t, "testdata")) + t.Parallel() + + RunGnolandTestscripts(t, "testdata") } diff --git a/gno.land/pkg/integration/testdata/gnokey.txtar b/gno.land/pkg/integration/testdata/gnokey.txtar index e4d2c93c0c9..123a0ce291c 100644 --- a/gno.land/pkg/integration/testdata/gnokey.txtar +++ b/gno.land/pkg/integration/testdata/gnokey.txtar @@ -6,27 +6,19 @@ gnoland start ## test1 account should be available on default gnokey query auth/accounts/${USER_ADDR_test1} -cmp stdout gnokey-query-valid.stdout.golden -cmp stderr gnokey-query-valid.stderr.golden +stdout 'height: 0' +stdout 'data: {' +stdout ' "BaseAccount": {' +stdout ' "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",' +stdout ' "coins": "[0-9]*ugnot",' # dynamic +stdout ' "public_key": null,' +stdout ' "account_number": "0",' +stdout ' "sequence": "0"' +stdout ' }' +stdout '}' +! stderr '.+' # empty ## invalid gnokey command should raise an error ! gnokey query foo/bar -cmp stdout gnokey-query-invalid.stdout.golden -cmp stderr gnokey-query-invalid.stderr.golden - --- gnokey-query-valid.stdout.golden -- -height: 0 -data: { - "BaseAccount": { - "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - "coins": "9999892000000ugnot", - "public_key": null, - "account_number": "0", - "sequence": "0" - } -} --- gnokey-query-valid.stderr.golden -- --- gnokey-query-invalid.stdout.golden -- -Log: --- gnokey-query-invalid.stderr.golden -- -"gnokey" error: unknown request error +stdout 'Log:' +stderr '"gnokey" error: unknown request error' diff --git a/gno.land/pkg/integration/testing.go b/gno.land/pkg/integration/testing.go new file mode 100644 index 00000000000..7803e213da1 --- /dev/null +++ b/gno.land/pkg/integration/testing.go @@ -0,0 +1,39 @@ +package integration + +import ( + "errors" + + "github.com/jaekwon/testify/assert" + "github.com/jaekwon/testify/require" + "github.com/rogpeppe/go-internal/testscript" +) + +// This error is from testscript.Fatalf and is needed to correctly +// handle the FailNow method. +// see: https://github.com/rogpeppe/go-internal/blob/32ae33786eccde1672d4ba373c80e1bc282bfbf6/testscript/testscript.go#L799-L812 +var errFailNow = errors.New("fail now!") //nolint:stylecheck + +var ( + _ require.TestingT = (*testingTS)(nil) + _ assert.TestingT = (*testingTS)(nil) +) + +type TestingTS = require.TestingT + +type testingTS struct { + *testscript.TestScript +} + +func TSTestingT(ts *testscript.TestScript) TestingTS { + return &testingTS{ts} +} + +func (t *testingTS) Errorf(format string, args ...interface{}) { + defer recover() // we can ignore recover result, we just want to catch it up + t.Fatalf(format, args...) +} + +func (t *testingTS) FailNow() { + // unfortunately we can't access underlying `t.t.FailNow` method + panic(errFailNow) +} diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index f0a696ddd85..b10686b0105 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -8,60 +8,67 @@ import ( "path/filepath" "strconv" "strings" - "sync" "testing" - "time" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/node" - "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" - "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" "github.com/rogpeppe/go-internal/testscript" ) -// XXX: This should be centralize somewhere. -const ( - test1Addr = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" - test1Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" -) +type tSeqShim struct{ *testing.T } + +// noop Parallel method allow us to run test sequentially +func (tSeqShim) Parallel() {} + +func (t tSeqShim) Run(name string, f func(testscript.T)) { + t.T.Run(name, func(t *testing.T) { + f(tSeqShim{t}) + }) +} + +func (t tSeqShim) Verbose() bool { + return testing.Verbose() +} + +// RunGnolandTestscripts sets up and runs txtar integration tests for gnoland nodes. +// It prepares an in-memory gnoland node and initializes the necessary environment and custom commands. +// The function adapts the test setup for use with the testscript package, enabling +// the execution of gnoland and gnokey commands within txtar scripts. +// +// Refer to package documentation in doc.go for more information on commands and example txtar scripts. +func RunGnolandTestscripts(t *testing.T, txtarDir string) { + t.Helper() + + p := setupGnolandTestScript(t, txtarDir) + if deadline, ok := t.Deadline(); ok && p.Deadline.IsZero() { + p.Deadline = deadline + } + + testscript.RunT(tSeqShim{t}, p) +} type testNode struct { *node.Node - logger log.Logger nGnoKeyExec uint // Counter for execution of gnokey. } -// SetupGnolandTestScript prepares the test environment to execute txtar tests -// using a partial InMemory gnoland node. It initializes key storage, sets up the gnoland node, -// and provides custom commands like "gnoland" and "gnokey" for txtar script execution. -// -// The function returns testscript.Params which contain the test setup and command -// executions to be used with the testscript package. -// -// For a detailed explanation of the commands and their behaviors, as well as -// example txtar scripts, refer to the package documentation in doc.go. -func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { +func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { t.Helper() tmpdir := t.TempDir() // `gnoRootDir` should point to the local location of the gno repository. // It serves as the gno equivalent of GOROOT. - gnoRootDir := gnoland.GuessGnoRootDir() + gnoRootDir := gnoland.MustGuessGnoRootDir() // `gnoHomeDir` should be the local directory where gnokey stores keys. gnoHomeDir := filepath.Join(tmpdir, "gno") - // `gnoDataDir` should refer to the local location where the gnoland node - // stores its configuration and data. - gnoDataDir := filepath.Join(tmpdir, "data") - // Testscripts run concurrently by default, so we need to be prepared for that. - var muNodes sync.Mutex nodes := map[string]*testNode{} updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS")) @@ -76,10 +83,35 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { return err } - // XXX: Add a command to add custom account. - kb.CreateAccount("test1", test1Seed, "", "", 0, 0) - env.Setenv("USER_SEED_test1", test1Seed) - env.Setenv("USER_ADDR_test1", test1Addr) + // create sessions ID + var sid string + { + works := env.Getenv("WORK") + sum := crc32.ChecksumIEEE([]byte(works)) + sid = strconv.FormatUint(uint64(sum), 16) + env.Setenv("SID", sid) + } + + // setup logger + var logger log.Logger + { + logger = log.NewNopLogger() + if persistWorkDir || os.Getenv("LOG_DIR") != "" { + logname := fmt.Sprintf("gnoland-%s.log", sid) + logger, err = getTestingLogger(env, logname) + if err != nil { + return fmt.Errorf("unable to setup logger: %w", err) + } + } + + env.Values["_logger"] = logger + } + + // Setup "test1" default account + kb.CreateAccount(DefaultAccount_Name, DefaultAccount_Seed, "", "", 0, 0) + + env.Setenv("USER_SEED_"+DefaultAccount_Name, DefaultAccount_Seed) + env.Setenv("USER_ADDR_"+DefaultAccount_Name, DefaultAccount_Address) env.Setenv("GNOROOT", gnoRootDir) env.Setenv("GNOHOME", gnoHomeDir) @@ -88,15 +120,13 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { }, Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ "gnoland": func(ts *testscript.TestScript, neg bool, args []string) { - muNodes.Lock() - defer muNodes.Unlock() - if len(args) == 0 { tsValidateError(ts, "gnoland", neg, fmt.Errorf("syntax: gnoland [start|stop]")) return } - sid := getSessionID(ts) + logger := ts.Value("_logger").(log.Logger) // grab logger + sid := ts.Getenv("SID") // grab session id var cmd string cmd, args = args[0], args[1:] @@ -109,63 +139,20 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { break } - logger := log.NewNopLogger() - if persistWorkDir || os.Getenv("LOG_DIR") != "" { - logname := fmt.Sprintf("gnoland-%s.log", sid) - logger = getTestingLogger(ts, logname) - } + // Warp up `ts` so we can pass it to other testing method + t := TSTestingT(ts) - dataDir := filepath.Join(gnoDataDir, sid) - var node *node.Node - if node, err = execTestingGnoland(t, logger, dataDir, gnoRootDir, args); err == nil { - nodes[sid] = &testNode{ - Node: node, - logger: logger, - } - ts.Defer(func() { - muNodes.Lock() - defer muNodes.Unlock() - - if n := nodes[sid]; n != nil { - if err := n.Stop(); err != nil { - panic(fmt.Errorf("node %q was unable to stop: %w", sid, err)) - } - } - }) - - // Get listen address environment. - // It should have been updated with the right port on start. - laddr := node.Config().RPC.ListenAddress - - // Add default environements. - ts.Setenv("RPC_ADDR", laddr) - ts.Setenv("GNODATA", gnoDataDir) - - const listenerID = "testing_listener" - - // Wait for first block by waiting for `EventNewBlock` event. - nb := make(chan struct{}, 1) - node.EventSwitch().AddListener(listenerID, func(ev events.Event) { - if _, ok := ev.(types.EventNewBlock); ok { - select { - case nb <- struct{}{}: - default: - } - } - }) - - if node.BlockStore().Height() == 0 { - select { - case <-nb: // ok - case <-time.After(time.Second * 6): - ts.Fatalf("timeout while waiting for the node to start") - } - } - - node.EventSwitch().RemoveListener(listenerID) - - fmt.Fprintln(ts.Stdout(), "node started successfully") - } + // Generate config and node + cfg, _ := TestingNodeConfig(t, gnoRootDir) + n, remoteAddr := TestingInMemoryNode(t, logger, cfg) + + // Register cleanup + nodes[sid] = &testNode{Node: n} + + // Add default environements + ts.Setenv("RPC_ADDR", remoteAddr) + + fmt.Fprintln(ts.Stdout(), "node started successfully") case "stop": n, ok := nodes[sid] if !ok { @@ -176,9 +163,8 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { if err = n.Stop(); err == nil { delete(nodes, sid) - // Unset gnoland environements. + // Unset gnoland environements ts.Setenv("RPC_ADDR", "") - ts.Setenv("GNODATA", "") fmt.Fprintln(ts.Stdout(), "node stopped successfully") } default: @@ -188,12 +174,10 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { tsValidateError(ts, "gnoland "+cmd, neg, err) }, "gnokey": func(ts *testscript.TestScript, neg bool, args []string) { - muNodes.Lock() - defer muNodes.Unlock() + logger := ts.Value("_logger").(log.Logger) // grab logger + sid := ts.Getenv("SID") // grab session id - sid := getSessionID(ts) - - // Setup IO command. + // Setup IO command io := commands.NewTestIO() io.SetOut(commands.WriteNopCloser(ts.Stdout())) io.SetErr(commands.WriteNopCloser(ts.Stderr())) @@ -212,9 +196,10 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { n.nGnoKeyExec++ headerlog := fmt.Sprintf("%.02d!EXEC_GNOKEY", n.nGnoKeyExec) + // Log the command inside gnoland logger, so we can better scope errors. - n.logger.Info(headerlog, strings.Join(args, " ")) - defer n.logger.Info(headerlog, "END") + logger.Info(headerlog, strings.Join(args, " ")) + defer logger.Info(headerlog, "END") } // Inject default argument, if duplicate @@ -230,35 +215,30 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { } } -func getSessionID(ts *testscript.TestScript) string { - works := ts.Getenv("WORK") - sum := crc32.ChecksumIEEE([]byte(works)) - return strconv.FormatUint(uint64(sum), 16) -} - -func getTestingLogger(ts *testscript.TestScript, logname string) log.Logger { +func getTestingLogger(env *testscript.Env, logname string) (log.Logger, error) { var path string + if logdir := os.Getenv("LOG_DIR"); logdir != "" { if err := os.MkdirAll(logdir, 0o755); err != nil { - ts.Fatalf("unable to make log directory %q", logdir) + return nil, fmt.Errorf("unable to make log directory %q", logdir) } var err error if path, err = filepath.Abs(filepath.Join(logdir, logname)); err != nil { - ts.Fatalf("uanble to get absolute path of logdir %q", logdir) + return nil, fmt.Errorf("uanble to get absolute path of logdir %q", logdir) } - } else if workdir := ts.Getenv("WORK"); workdir != "" { + } else if workdir := env.Getenv("WORK"); workdir != "" { path = filepath.Join(workdir, logname) } else { - return log.NewNopLogger() + return log.NewNopLogger(), nil } f, err := os.Create(path) if err != nil { - ts.Fatalf("unable to create log file %q: %s", path, err.Error()) + return nil, fmt.Errorf("unable to create log file %q: %w", path, err) } - ts.Defer(func() { + env.Defer(func() { if err := f.Close(); err != nil { panic(fmt.Errorf("unable to close log file %q: %w", path, err)) } @@ -274,9 +254,22 @@ func getTestingLogger(ts *testscript.TestScript, logname string) log.Logger { logger.SetLevel(log.LevelInfo) case "": default: - ts.Fatalf("invalid log level %q", level) + return nil, fmt.Errorf("invalid log level %q", level) } - ts.Logf("starting logger: %q", path) - return logger + env.T().Log("starting logger: %q", path) + return logger, nil +} + +func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { + if err != nil { + fmt.Fprintf(ts.Stderr(), "%q error: %v\n", cmd, err) + if !neg { + ts.Fatalf("unexpected %q command failure: %s", cmd, err) + } + } else { + if neg { + ts.Fatalf("unexpected %q command success", cmd) + } + } } diff --git a/gno.land/pkg/integration/testing_node.go b/gno.land/pkg/integration/testing_node.go new file mode 100644 index 00000000000..1ca7e11eb63 --- /dev/null +++ b/gno.land/pkg/integration/testing_node.go @@ -0,0 +1,184 @@ +package integration + +import ( + "path/filepath" + "sync" + "time" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" + "github.com/gnolang/gno/tm2/pkg/bft/node" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/jaekwon/testify/require" +) + +const ( + DefaultAccount_Name = "test1" + DefaultAccount_Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + DefaultAccount_Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" +) + +// TestingInMemoryNode initializes and starts an in-memory node for testing. +// It returns the node instance and its RPC remote address. +func TestingInMemoryNode(t TestingTS, logger log.Logger, config *gnoland.InMemoryNodeConfig) (*node.Node, string) { + node, err := gnoland.NewInMemoryNode(logger, config) + require.NoError(t, err) + + err = node.Start() + require.NoError(t, err) + + select { + case <-waitForNodeReadiness(node): + case <-time.After(time.Second * 6): + require.FailNow(t, "timeout while waiting for the node to start") + } + + return node, node.Config().RPC.ListenAddress +} + +// TestingNodeConfig constructs an in-memory node configuration +// with default packages and genesis transactions already loaded. +// It will return the default creator address of the loaded packages. +func TestingNodeConfig(t TestingTS, gnoroot string) (*gnoland.InMemoryNodeConfig, bft.Address) { + cfg := TestingMinimalNodeConfig(t, gnoroot) + + creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 + + balances := LoadDefaultGenesisBalanceFile(t, gnoroot) + txs := []std.Tx{} + txs = append(txs, LoadDefaultPackages(t, creator, gnoroot)...) + txs = append(txs, LoadDefaultGenesisTXsFile(t, cfg.Genesis.ChainID, gnoroot)...) + + cfg.Genesis.AppState = gnoland.GnoGenesisState{ + Balances: balances, + Txs: txs, + } + + return cfg, creator +} + +// TestingMinimalNodeConfig constructs the default minimal in-memory node configuration for testing. +func TestingMinimalNodeConfig(t TestingTS, gnoroot string) *gnoland.InMemoryNodeConfig { + tmconfig := DefaultTestingTMConfig(gnoroot) + + // Create Mocked Identity + pv := gnoland.NewMockedPrivValidator() + + // Generate genesis config + genesis := DefaultTestingGenesisConfig(t, gnoroot, pv.GetPubKey(), tmconfig) + + return &gnoland.InMemoryNodeConfig{ + PrivValidator: pv, + Genesis: genesis, + TMConfig: tmconfig, + } +} + +func DefaultTestingGenesisConfig(t TestingTS, gnoroot string, self crypto.PubKey, tmconfig *tmcfg.Config) *bft.GenesisDoc { + return &bft.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: tmconfig.ChainID(), + ConsensusParams: abci.ConsensusParams{ + Block: &abci.BlockParams{ + MaxTxBytes: 1_000_000, // 1MB, + MaxDataBytes: 2_000_000, // 2MB, + MaxGas: 10_0000_000, // 10M gas + TimeIotaMS: 100, // 100ms + }, + }, + Validators: []bft.GenesisValidator{ + { + Address: self.Address(), + PubKey: self, + Power: 10, + Name: "self", + }, + }, + AppState: gnoland.GnoGenesisState{ + Balances: []gnoland.Balance{ + { + Address: crypto.MustAddressFromString(DefaultAccount_Address), + Amount: std.MustParseCoins("10000000000000ugnot"), + }, + }, + Txs: []std.Tx{}, + }, + } +} + +// LoadDefaultPackages loads the default packages for testing using a given creator address and gnoroot directory. +func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []std.Tx { + examplesDir := filepath.Join(gnoroot, "examples") + + defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot")) + defaultCreator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 + txs, err := gnoland.LoadPackagesFromDir(examplesDir, defaultCreator, defaultFee, nil) + require.NoError(t, err) + + return txs +} + +// LoadDefaultGenesisBalanceFile loads the default genesis balance file for testing. +func LoadDefaultGenesisBalanceFile(t TestingTS, gnoroot string) []gnoland.Balance { + balanceFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_balances.txt") + + genesisBalances, err := gnoland.LoadGenesisBalancesFile(balanceFile) + require.NoError(t, err) + + return genesisBalances +} + +// LoadDefaultGenesisTXsFile loads the default genesis transactions file for testing. +func LoadDefaultGenesisTXsFile(t TestingTS, chainid string, gnoroot string) []std.Tx { + txsFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_txs.txt") + + // NOTE: We dont care about giving a correct address here, as it's only for display + // XXX: Do we care loading this TXs for testing ? + genesisTXs, err := gnoland.LoadGenesisTxsFile(txsFile, chainid, "https://127.0.0.1:26657") + require.NoError(t, err) + + return genesisTXs +} + +// DefaultTestingTMConfig constructs the default Tendermint configuration for testing. +func DefaultTestingTMConfig(gnoroot string) *tmcfg.Config { + const defaultListner = "tcp://127.0.0.1:0" + + tmconfig := tmcfg.TestConfig().SetRootDir(gnoroot) + tmconfig.Consensus.CreateEmptyBlocks = true + tmconfig.Consensus.CreateEmptyBlocksInterval = time.Duration(0) + tmconfig.RPC.ListenAddress = defaultListner + tmconfig.P2P.ListenAddress = defaultListner + return tmconfig +} + +// waitForNodeReadiness waits until the node is ready, signaling via the EventNewBlock event. +// XXX: This should be replace by https://github.com/gnolang/gno/pull/1216 +func waitForNodeReadiness(n *node.Node) <-chan struct{} { + const listenerID = "first_block_listener" + + var once sync.Once + + nb := make(chan struct{}) + ready := func() { + close(nb) + n.EventSwitch().RemoveListener(listenerID) + } + + n.EventSwitch().AddListener(listenerID, func(ev events.Event) { + if _, ok := ev.(bft.EventNewBlock); ok { + once.Do(ready) + } + }) + + if n.BlockStore().Height() > 0 { + once.Do(ready) + } + + return nb +} diff --git a/gnovm/Makefile b/gnovm/Makefile index 5fcacf94f62..34e94f88633 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -46,15 +46,15 @@ _test.pkg: .PHONY: _test.gnolang _test.gnolang: _test.gnolang.native _test.gnolang.stdlibs _test.gnolang.realm _test.gnolang.pkg0 _test.gnolang.pkg1 _test.gnolang.pkg2 _test.gnolang.other -_test.gnolang.other:; go test $(GOTEST_FLAGS) tests/*.go -run "(TestFileStr|TestSelectors)" -_test.gnolang.realm:; go test $(GOTEST_FLAGS) tests/*.go -run "TestFiles/^zrealm" -_test.gnolang.pkg0:; go test $(GOTEST_FLAGS) tests/*.go -run "TestPackages/(bufio|crypto|encoding|errors|internal|io|math|sort|std|stdshim|strconv|strings|testing|unicode)" -_test.gnolang.pkg1:; go test $(GOTEST_FLAGS) tests/*.go -run "TestPackages/regexp" -_test.gnolang.pkg2:; go test $(GOTEST_FLAGS) tests/*.go -run "TestPackages/bytes" -_test.gnolang.native:; go test $(GOTEST_FLAGS) tests/*.go -test.short -run "TestFilesNative/" -_test.gnolang.stdlibs:; go test $(GOTEST_FLAGS) tests/*.go -test.short -run 'TestFiles$$/' -_test.gnolang.native.sync:; go test $(GOTEST_FLAGS) tests/*.go -test.short -run "TestFilesNative/" --update-golden-tests -_test.gnolang.stdlibs.sync:; go test $(GOTEST_FLAGS) tests/*.go -test.short -run 'TestFiles$$/' --update-golden-tests +_test.gnolang.other:; go test tests/*.go -run "(TestFileStr|TestSelectors)" $(GOTEST_FLAGS) +_test.gnolang.realm:; go test tests/*.go -run "TestFiles/^zrealm" $(GOTEST_FLAGS) +_test.gnolang.pkg0:; go test tests/*.go -run "TestPackages/(bufio|crypto|encoding|errors|internal|io|math|sort|std|stdshim|strconv|strings|testing|unicode)" $(GOTEST_FLAGS) +_test.gnolang.pkg1:; go test tests/*.go -run "TestPackages/regexp" $(GOTEST_FLAGS) +_test.gnolang.pkg2:; go test tests/*.go -run "TestPackages/bytes" $(GOTEST_FLAGS) +_test.gnolang.native:; go test tests/*.go -test.short -run "TestFilesNative/" $(GOTEST_FLAGS) +_test.gnolang.stdlibs:; go test tests/*.go -test.short -run 'TestFiles$$/' $(GOTEST_FLAGS) +_test.gnolang.native.sync:; go test tests/*.go -test.short -run "TestFilesNative/" --update-golden-tests $(GOTEST_FLAGS) +_test.gnolang.stdlibs.sync:; go test tests/*.go -test.short -run 'TestFiles$$/' --update-golden-tests $(GOTEST_FLAGS) ######################################## # Code gen diff --git a/gnovm/cmd/gno/build.go b/gnovm/cmd/gno/build.go index b80711586e4..7396d17fd8f 100644 --- a/gnovm/cmd/gno/build.go +++ b/gnovm/cmd/gno/build.go @@ -20,7 +20,7 @@ var defaultBuildOptions = &buildCfg{ goBinary: "go", } -func newBuildCmd(io *commands.IO) *commands.Command { +func newBuildCmd(io commands.IO) *commands.Command { cfg := &buildCfg{} return commands.NewCommand( @@ -52,7 +52,7 @@ func (c *buildCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execBuild(cfg *buildCfg, args []string, io *commands.IO) error { +func execBuild(cfg *buildCfg, args []string, io commands.IO) error { if len(args) < 1 { return flag.ErrHelp } diff --git a/gnovm/cmd/gno/build_test.go b/gnovm/cmd/gno/build_test.go index 89339ee8a6e..81aed7d1c79 100644 --- a/gnovm/cmd/gno/build_test.go +++ b/gnovm/cmd/gno/build_test.go @@ -1,17 +1,25 @@ package main -import "testing" +import ( + "testing" -func TestBuildApp(t *testing.T) { - tc := []testMainCase{ - { - args: []string{"build"}, - errShouldBe: "flag: help requested", - }, + "github.com/gnolang/gno/gnovm/pkg/integration" + "github.com/rogpeppe/go-internal/testscript" + "github.com/stretchr/testify/require" +) - // {args: []string{"build", "..."}, stdoutShouldContain: "..."}, - // TODO: auto precompilation - // TODO: error handling +func Test_ScriptsBuild(t *testing.T) { + p := testscript.Params{ + Dir: "testdata/gno_build", } - testMainCaseRun(t, tc) + + if coverdir, ok := integration.ResolveCoverageDir(); ok { + err := integration.SetupTestscriptsCoverage(&p, coverdir) + require.NoError(t, err) + } + + err := integration.SetupGno(&p, t.TempDir()) + require.NoError(t, err) + + testscript.Run(t, p) } diff --git a/gnovm/cmd/gno/clean.go b/gnovm/cmd/gno/clean.go index 999cc5f1f53..dad5332c4fa 100644 --- a/gnovm/cmd/gno/clean.go +++ b/gnovm/cmd/gno/clean.go @@ -20,7 +20,7 @@ type cleanCfg struct { modCache bool // clean -modcache flag } -func newCleanCmd(io *commands.IO) *commands.Command { +func newCleanCmd(io commands.IO) *commands.Command { cfg := &cleanCfg{} return commands.NewCommand( @@ -59,7 +59,7 @@ func (c *cleanCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execClean(cfg *cleanCfg, args []string, io *commands.IO) error { +func execClean(cfg *cleanCfg, args []string, io commands.IO) error { if len(args) > 0 { return flag.ErrHelp } @@ -96,7 +96,7 @@ func execClean(cfg *cleanCfg, args []string, io *commands.IO) error { } // clean removes generated files from a directory. -func clean(dir string, cfg *cleanCfg, io *commands.IO) error { +func clean(dir string, cfg *cleanCfg, io commands.IO) error { return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil { return err diff --git a/gnovm/cmd/gno/doc.go b/gnovm/cmd/gno/doc.go index 0de49470c2a..4617c31741e 100644 --- a/gnovm/cmd/gno/doc.go +++ b/gnovm/cmd/gno/doc.go @@ -22,7 +22,7 @@ type docCfg struct { rootDir string } -func newDocCmd(io *commands.IO) *commands.Command { +func newDocCmd(io commands.IO) *commands.Command { c := &docCfg{} return commands.NewCommand( commands.Metadata{ @@ -74,7 +74,7 @@ func (c *docCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execDoc(cfg *docCfg, args []string, io *commands.IO) error { +func execDoc(cfg *docCfg, args []string, io commands.IO) error { // guess opts.RootDir if cfg.rootDir == "" { cfg.rootDir = guessRootDir() @@ -112,7 +112,7 @@ func execDoc(cfg *docCfg, args []string, io *commands.IO) error { io.Printfln("warning: error parsing some candidate packages:\n%v", err) } return res.WriteDocumentation( - io.Out, + io.Out(), &doc.WriteDocumentationOptions{ ShowAll: cfg.all, Source: cfg.src, diff --git a/gnovm/cmd/gno/doc_test.go b/gnovm/cmd/gno/doc_test.go index 3eb90e2a329..513862ad2dc 100644 --- a/gnovm/cmd/gno/doc_test.go +++ b/gnovm/cmd/gno/doc_test.go @@ -10,7 +10,7 @@ func TestGnoDoc(t *testing.T) { }, { args: []string{"doc", "avl"}, - stdoutShouldContain: "func NewNode", + stdoutShouldContain: "func NewTree", }, { args: []string{"doc", "-u", "avl.Node"}, diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 158b9d8db5d..ad223afa3b0 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -19,7 +19,7 @@ type lintCfg struct { // auto-fix: apply suggested fixes automatically. } -func newLintCmd(io *commands.IO) *commands.Command { +func newLintCmd(io commands.IO) *commands.Command { cfg := &lintCfg{} return commands.NewCommand( @@ -38,10 +38,10 @@ func newLintCmd(io *commands.IO) *commands.Command { func (c *lintCfg) RegisterFlags(fs *flag.FlagSet) { fs.BoolVar(&c.verbose, "verbose", false, "verbose output when lintning") fs.StringVar(&c.rootDir, "root-dir", "", "clone location of github.com/gnolang/gno (gno tries to guess it)") - fs.IntVar(&c.setExitStatus, "set_exit_status", 1, "set exit status to 1 if any issues are found") + fs.IntVar(&c.setExitStatus, "set-exit-status", 1, "set exit status to 1 if any issues are found") } -func execLint(cfg *lintCfg, args []string, io *commands.IO) error { +func execLint(cfg *lintCfg, args []string, io commands.IO) error { if len(args) < 1 { return flag.ErrHelp } @@ -62,12 +62,12 @@ func execLint(cfg *lintCfg, args []string, io *commands.IO) error { hasError := false addIssue := func(issue lintIssue) { hasError = true - fmt.Fprint(io.Err, issue.String()+"\n") + fmt.Fprint(io.Err(), issue.String()+"\n") } for _, pkgPath := range pkgPaths { if verbose { - fmt.Fprintf(io.Err, "Linting %q...\n", pkgPath) + fmt.Fprintf(io.Err(), "Linting %q...\n", pkgPath) } // 'gno.mod' exists? diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go index ce200a1fedd..0a747a03778 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/lint_test.go @@ -8,16 +8,16 @@ func TestLintApp(t *testing.T) { args: []string{"lint"}, errShouldBe: "flag: help requested", }, { - args: []string{"lint", "--set_exit_status=0", "../../tests/integ/run-main/"}, + args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run-main/"}, stderrShouldContain: "./../../tests/integ/run-main: missing 'gno.mod' file (code=1).", }, { - args: []string{"lint", "--set_exit_status=0", "../../tests/integ/run-main/"}, + args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run-main/"}, stderrShouldContain: "./../../tests/integ/run-main: missing 'gno.mod' file (code=1).", }, { - args: []string{"lint", "--set_exit_status=0", "../../tests/integ/minimalist-gnomod/"}, + args: []string{"lint", "--set-exit-status=0", "../../tests/integ/minimalist-gnomod/"}, // TODO: raise an error because there is a gno.mod, but no .gno files }, { - args: []string{"lint", "--set_exit_status=0", "../../tests/integ/invalid-module-name/"}, + args: []string{"lint", "--set-exit-status=0", "../../tests/integ/invalid-module-name/"}, // TODO: raise an error because gno.mod is invalid }, // TODO: 'gno mod' is valid? diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index 433db703b90..c84aa52fc86 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -18,7 +18,7 @@ func main() { } } -func newGnocliCmd(io *commands.IO) *commands.Command { +func newGnocliCmd(io commands.IO) *commands.Command { cmd := commands.NewCommand( commands.Metadata{ ShortUsage: " [flags] [...]", diff --git a/gnovm/cmd/gno/main_test.go b/gnovm/cmd/gno/main_test.go index 8d19a50e814..371cdf913e8 100644 --- a/gnovm/cmd/gno/main_test.go +++ b/gnovm/cmd/gno/main_test.go @@ -5,12 +5,10 @@ import ( "context" "fmt" "os" - "os/exec" "path/filepath" "strings" "testing" - "github.com/rogpeppe/go-internal/testscript" "github.com/stretchr/testify/require" "github.com/gnolang/gno/tm2/pkg/commands" @@ -144,45 +142,3 @@ func testMainCaseRun(t *testing.T, tc []testMainCase) { }) } } - -func setupTestScript(t *testing.T, txtarDir string) testscript.Params { - t.Helper() - // Get root location of github.com/gnolang/gno - goModPath, err := exec.Command("go", "env", "GOMOD").CombinedOutput() - require.NoError(t, err) - rootDir := filepath.Dir(string(goModPath)) - // Build a fresh gno binary in a temp directory - gnoBin := filepath.Join(t.TempDir(), "gno") - err = exec.Command("go", "build", "-o", gnoBin, filepath.Join(rootDir, "gnovm", "cmd", "gno")).Run() - require.NoError(t, err) - // Define script params - return testscript.Params{ - Setup: func(env *testscript.Env) error { - env.Vars = append(env.Vars, - "GNOROOT="+rootDir, // thx PR 1014 :) - // by default, $HOME=/no-home, but we need an existing $HOME directory - // because some commands needs to access $HOME/.cache/go-build - "HOME="+t.TempDir(), - ) - return nil - }, - Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ - // add a custom "gno" command so txtar files can easily execute "gno" - // without knowing where is the binary or how it is executed. - "gno": func(ts *testscript.TestScript, neg bool, args []string) { - err := ts.Exec(gnoBin, args...) - if err != nil { - ts.Logf("[%v]\n", err) - if !neg { - ts.Fatalf("unexpected gno command failure") - } - } else { - if neg { - ts.Fatalf("unexpected gno command success") - } - } - }, - }, - Dir: txtarDir, - } -} diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 267b7d99237..da8db3ed68a 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -4,8 +4,12 @@ import ( "context" "flag" "fmt" + "go/parser" + "go/token" "os" "path/filepath" + "sort" + "strings" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/tm2/pkg/commands" @@ -17,7 +21,7 @@ type modDownloadCfg struct { verbose bool } -func newModCmd(io *commands.IO) *commands.Command { +func newModCmd(io commands.IO) *commands.Command { cmd := commands.NewCommand( commands.Metadata{ Name: "mod", @@ -31,12 +35,13 @@ func newModCmd(io *commands.IO) *commands.Command { cmd.AddSubCommands( newModDownloadCmd(io), newModInitCmd(), + newModTidy(io), ) return cmd } -func newModDownloadCmd(io *commands.IO) *commands.Command { +func newModDownloadCmd(io commands.IO) *commands.Command { cfg := &modDownloadCfg{} return commands.NewCommand( @@ -66,6 +71,20 @@ func newModInitCmd() *commands.Command { ) } +func newModTidy(io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "tidy", + ShortUsage: "tidy", + ShortHelp: "Add missing and remove unused modules", + }, + commands.NewEmptyConfig(), + func(_ context.Context, args []string) error { + return execModTidy(args, io) + }, + ) +} + func (c *modDownloadCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.remote, @@ -82,7 +101,7 @@ func (c *modDownloadCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execModDownload(cfg *modDownloadCfg, args []string, io *commands.IO) error { +func execModDownload(cfg *modDownloadCfg, args []string, io commands.IO) error { if len(args) > 0 { return flag.ErrHelp } @@ -152,3 +171,89 @@ func execModInit(args []string) error { return nil } + +func execModTidy(args []string, io commands.IO) error { + if len(args) > 0 { + return flag.ErrHelp + } + + wd, err := os.Getwd() + if err != nil { + return err + } + fname := filepath.Join(wd, "gno.mod") + gm, err := gnomod.ParseGnoMod(fname) + if err != nil { + return err + } + + // Drop all existing requires + for _, r := range gm.Require { + gm.DropRequire(r.Mod.Path) + } + + imports, err := getGnoImports(wd) + if err != nil { + return err + } + for _, im := range imports { + // skip if importpath is modulepath + if im == gm.Module.Mod.Path { + continue + } + gm.AddRequire(im, "v0.0.0-latest") + } + + gm.Write(fname) + return nil +} + +// getGnoImports returns the list of gno imports from a given path. +// Note: It ignores subdirs. Since right now we are still deciding on +// how to handle subdirs. +// See: +// - https://github.com/gnolang/gno/issues/1024 +// - https://github.com/gnolang/gno/issues/852 +// +// TODO: move this to better location. +func getGnoImports(path string) ([]string, error) { + entries, err := os.ReadDir(path) + if err != nil { + return nil, err + } + + allImports := make([]string, 0) + seen := make(map[string]struct{}) + for _, e := range entries { + filename := e.Name() + if ext := filepath.Ext(filename); ext != ".gno" { + continue + } + if strings.HasSuffix(filename, "_filetest.gno") { + continue + } + data, err := os.ReadFile(filepath.Join(path, filename)) + if err != nil { + return nil, err + } + fs := token.NewFileSet() + f, err := parser.ParseFile(fs, filename, data, parser.ImportsOnly) + if err != nil { + return nil, err + } + for _, imp := range f.Imports { + importPath := strings.TrimPrefix(strings.TrimSuffix(imp.Path.Value, `"`), `"`) + if !strings.HasPrefix(importPath, "gno.land/") { + continue + } + if _, ok := seen[importPath]; ok { + continue + } + allImports = append(allImports, importPath) + seen[importPath] = struct{}{} + } + } + sort.Strings(allImports) + + return allImports, nil +} diff --git a/gnovm/cmd/gno/mod_test.go b/gnovm/cmd/gno/mod_test.go index fdae3d12c7a..bbf106c8960 100644 --- a/gnovm/cmd/gno/mod_test.go +++ b/gnovm/cmd/gno/mod_test.go @@ -1,6 +1,13 @@ package main -import "testing" +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) func TestModApp(t *testing.T) { tc := []testMainCase{ @@ -9,7 +16,7 @@ func TestModApp(t *testing.T) { errShouldBe: "flag: help requested", }, - // test gno.mod download + // test `gno mod download` { args: []string{"mod", "download"}, testDir: "../../tests/integ/empty-dir", @@ -73,7 +80,7 @@ func TestModApp(t *testing.T) { errShouldContain: "fetch: writepackage: querychain", }, - // test gno.mod init with no module name + // test `gno mod init` with no module name { args: []string{"mod", "init"}, testDir: "../../tests/integ/valid1", @@ -110,7 +117,7 @@ func TestModApp(t *testing.T) { errShouldBe: "create gno.mod file: gno.mod file already exists", }, - // test gno.mod init with module name + // test `gno mod init` with module name { args: []string{"mod", "init", "gno.land/p/demo/foo"}, testDir: "../../tests/integ/empty-dir", @@ -137,6 +144,164 @@ func TestModApp(t *testing.T) { simulateExternalRepo: true, errShouldBe: "create gno.mod file: gno.mod file already exists", }, + + // test `gno mod tidy` with module name + { + args: []string{"mod", "tidy", "arg1"}, + testDir: "../../tests/integ/minimalist-gnomod", + simulateExternalRepo: true, + errShouldContain: "flag: help requested", + }, + { + args: []string{"mod", "tidy"}, + testDir: "../../tests/integ/empty-dir", + simulateExternalRepo: true, + errShouldContain: "could not read gno.mod file", + }, + { + args: []string{"mod", "tidy"}, + testDir: "../../tests/integ/invalid-module-version1", + simulateExternalRepo: true, + errShouldContain: "error parsing gno.mod file at", + }, + { + args: []string{"mod", "tidy"}, + testDir: "../../tests/integ/minimalist-gnomod", + simulateExternalRepo: true, + }, + { + args: []string{"mod", "tidy"}, + testDir: "../../tests/integ/require-remote-module", + simulateExternalRepo: true, + }, + { + args: []string{"mod", "tidy"}, + testDir: "../../tests/integ/valid2", + simulateExternalRepo: true, + }, + { + args: []string{"mod", "tidy"}, + testDir: "../../tests/integ/invalid-gno-file", + simulateExternalRepo: true, + errShouldContain: "expected 'package', found packag", + }, } testMainCaseRun(t, tc) } + +func TestGetGnoImports(t *testing.T) { + workingDir, err := os.Getwd() + require.NoError(t, err) + + // create external dir + tmpDir, cleanUpFn := createTmpDir(t) + defer cleanUpFn() + + // cd to tmp directory + os.Chdir(tmpDir) + defer os.Chdir(workingDir) + + files := []struct { + name, data string + }{ + { + name: "file1.gno", + data: ` + package tmp + + import ( + "std" + + "gno.land/p/demo/pkg1" + ) + `, + }, + { + name: "file2.gno", + data: ` + package tmp + + import ( + "gno.land/p/demo/pkg1" + "gno.land/p/demo/pkg2" + ) + `, + }, + { + name: "file1_test.gno", + data: ` + package tmp + + import ( + "testing" + + "gno.land/p/demo/testpkg" + ) + `, + }, + { + name: "z_0_filetest.gno", + data: ` + package main + + import ( + "gno.land/p/demo/filetestpkg" + ) + `, + }, + + // subpkg files + { + name: filepath.Join("subtmp", "file1.gno"), + data: ` + package subtmp + + import ( + "std" + + "gno.land/p/demo/subpkg1" + ) + `, + }, + { + name: filepath.Join("subtmp", "file2.gno"), + data: ` + package subtmp + + import ( + "gno.land/p/demo/subpkg1" + "gno.land/p/demo/subpkg2" + ) + `, + }, + } + + // Expected list of imports + // - ignore subdirs + // - ignore duplicate + // - ignore *_filetest.gno + // - should be sorted + expected := []string{ + "gno.land/p/demo/pkg1", + "gno.land/p/demo/pkg2", + "gno.land/p/demo/testpkg", + } + + // Create subpkg dir + err = os.Mkdir("subtmp", 0o700) + require.NoError(t, err) + + // Create files + for _, f := range files { + err = os.WriteFile(f.name, []byte(f.data), 0o644) + require.NoError(t, err) + } + + imports, err := getGnoImports(tmpDir) + require.NoError(t, err) + + require.Equal(t, len(expected), len(imports)) + for i := range imports { + assert.Equal(t, expected[i], imports[i]) + } +} diff --git a/gnovm/cmd/gno/precompile.go b/gnovm/cmd/gno/precompile.go index 7e895109706..4a4528001a2 100644 --- a/gnovm/cmd/gno/precompile.go +++ b/gnovm/cmd/gno/precompile.go @@ -47,7 +47,7 @@ func (p *precompileOptions) markAsPrecompiled(pkg importPath) { p.precompiled[pkg] = struct{}{} } -func newPrecompileCmd(io *commands.IO) *commands.Command { +func newPrecompileCmd(io commands.IO) *commands.Command { cfg := &precompileCfg{} return commands.NewCommand( @@ -107,7 +107,7 @@ func (c *precompileCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execPrecompile(cfg *precompileCfg, args []string, io *commands.IO) error { +func execPrecompile(cfg *precompileCfg, args []string, io commands.IO) error { if len(args) < 1 { return flag.ErrHelp } diff --git a/gnovm/cmd/gno/repl.go b/gnovm/cmd/gno/repl.go index 1acb96c3cb9..0a9d4934ce3 100644 --- a/gnovm/cmd/gno/repl.go +++ b/gnovm/cmd/gno/repl.go @@ -2,10 +2,11 @@ package main import ( "bufio" - "bytes" "context" + "errors" "flag" "fmt" + "go/scanner" "os" "strings" @@ -88,10 +89,10 @@ func execRepl(cfg *replCfg, args []string) error { // gno> import "gno.land/p/demo/avl" // import the p/demo/avl package // gno> func a() string { return "a" } // declare a new function named a // gno> /src // print current generated source -// gno> /editor // enter in editor mode to add several lines +// gno> /editor // enter in multi-line mode, end with ';' // gno> /reset // remove all previously inserted code // gno> println(a()) // print the result of calling a() -// gno> /exit +// gno> /exit // alternative to `) } @@ -99,30 +100,59 @@ func execRepl(cfg *replCfg, args []string) error { } func runRepl(cfg *replCfg) error { - // init repl state r := repl.NewRepl() if cfg.initialCommand != "" { handleInput(r, cfg.initialCommand) } - var multiline bool - for { - fmt.Fprint(os.Stdout, "gno> ") + fmt.Fprint(os.Stdout, "gno> ") - input, err := getInput(multiline) - if err != nil { - return err + inEdit := false + prev := "" + liner := bufio.NewScanner(os.Stdin) + + for liner.Scan() { + line := liner.Text() + + if l := strings.TrimSpace(line); l == ";" { + line, inEdit = "", false + } else if l == "/editor" { + line, inEdit = "", true + fmt.Fprintln(os.Stdout, "// enter a single ';' to quit and commit") + } + if prev != "" { + line = prev + "\n" + line + prev = "" + } + if inEdit { + fmt.Fprint(os.Stdout, "... ") + prev = line + continue } - multiline = handleInput(r, input) + if err := handleInput(r, line); err != nil { + var goScanError scanner.ErrorList + if errors.As(err, &goScanError) { + // We assune that a Go scanner error indicates an incomplete Go statement. + // Append next line and retry. + prev = line + } else { + fmt.Fprintln(os.Stderr, err) + } + } + + if prev == "" { + fmt.Fprint(os.Stdout, "gno> ") + } else { + fmt.Fprint(os.Stdout, "... ") + } } + return nil } -// handleInput reads the input string and parses it depending if it -// is a specific command, or source code. It returns true if the following -// input is expected to be on more than one line. -func handleInput(r *repl.Repl, input string) bool { +// handleInput executes specific "/" commands, or evaluates input as Gno source code. +func handleInput(r *repl.Repl, input string) error { switch strings.TrimSpace(input) { case "/reset": r.Reset() @@ -130,49 +160,14 @@ func handleInput(r *repl.Repl, input string) bool { fmt.Fprintln(os.Stdout, r.Src()) case "/exit": os.Exit(0) - case "/editor": - fmt.Fprintln(os.Stdout, "// Entering editor mode (^D to finish)") - return true case "": - // avoid to increase the repl execution counter if sending empty content - fmt.Fprintln(os.Stdout, "") - return false + // Avoid to increase the repl execution counter if no input. default: out, err := r.Process(input) if err != nil { - fmt.Fprintln(os.Stderr, err) + return err } fmt.Fprintln(os.Stdout, out) } - - return false -} - -const ( - inputBreaker = "^D" - nl = "\n" -) - -func getInput(ml bool) (string, error) { - s := bufio.NewScanner(os.Stdin) - var mlOut bytes.Buffer - for s.Scan() { - line := s.Text() - if !ml { - return line, nil - } - - if line == inputBreaker { - break - } - - mlOut.WriteString(line) - mlOut.WriteString(nl) - } - - if err := s.Err(); err != nil { - return "", err - } - - return mlOut.String(), nil + return nil } diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index 2c8d8c77350..d37c86df502 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -20,7 +20,7 @@ type runCfg struct { expr string } -func newRunCmd(io *commands.IO) *commands.Command { +func newRunCmd(io commands.IO) *commands.Command { cfg := &runCfg{} return commands.NewCommand( @@ -59,7 +59,7 @@ func (c *runCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execRun(cfg *runCfg, args []string, io *commands.IO) error { +func execRun(cfg *runCfg, args []string, io commands.IO) error { if len(args) == 0 { return flag.ErrHelp } @@ -68,9 +68,9 @@ func execRun(cfg *runCfg, args []string, io *commands.IO) error { cfg.rootDir = guessRootDir() } - stdin := io.In - stdout := io.Out - stderr := io.Err + stdin := io.In() + stdout := io.Out() + stderr := io.Err() // init store and machine testStore := tests.TestStore(cfg.rootDir, diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 8cacfd5623b..718235c1390 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -37,7 +37,7 @@ type testCfg struct { withNativeFallback bool } -func newTestCmd(io *commands.IO) *commands.Command { +func newTestCmd(io commands.IO) *commands.Command { cfg := &testCfg{} return commands.NewCommand( @@ -50,7 +50,7 @@ func newTestCmd(io *commands.IO) *commands.Command { 'gno test' recompiles each package along with any files with names matching the file pattern "*_test.gno" or "*_filetest.gno". -The only supported for now is a directory (relative or absolute). +The can be directory or file path (relative or absolute). - "*_test.gno" files work like "*_test.go" files, but they contain only test functions. Benchmark and fuzz functions aren't supported yet. Similarly, only @@ -157,7 +157,7 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execTest(cfg *testCfg, args []string, io *commands.IO) error { +func execTest(cfg *testCfg, args []string, io commands.IO) error { if len(args) < 1 { return flag.ErrHelp } @@ -182,9 +182,9 @@ func execTest(cfg *testCfg, args []string, io *commands.IO) error { cfg.rootDir = guessRootDir() } - paths, err := gnoPackagesFromArgs(args) + paths, err := targetsFromPatterns(args) if err != nil { - return fmt.Errorf("list package paths from args: %w", err) + return fmt.Errorf("list targets from patterns: %w", err) } if len(paths) == 0 { io.ErrPrintln("no packages to test") @@ -279,7 +279,7 @@ func gnoTestPkg( unittestFiles, filetestFiles []string, cfg *testCfg, - io *commands.IO, + io commands.IO, ) error { var ( verbose = cfg.verbose @@ -287,9 +287,9 @@ func gnoTestPkg( runFlag = cfg.run printRuntimeMetrics = cfg.printRuntimeMetrics - stdin = io.In - stdout = io.Out - stderr = io.Err + stdin = io.In() + stdout = io.Out() + stderr = io.Err() errs error ) @@ -415,7 +415,7 @@ func runTestFiles( verbose bool, printRuntimeMetrics bool, runFlag string, - io *commands.IO, + io commands.IO, ) error { var errs error diff --git a/gnovm/cmd/gno/test_test.go b/gnovm/cmd/gno/test_test.go index f5b069a5f03..b1dcfb21d29 100644 --- a/gnovm/cmd/gno/test_test.go +++ b/gnovm/cmd/gno/test_test.go @@ -3,9 +3,23 @@ package main import ( "testing" + "github.com/gnolang/gno/gnovm/pkg/integration" "github.com/rogpeppe/go-internal/testscript" + "github.com/stretchr/testify/require" ) -func TestTest(t *testing.T) { - testscript.Run(t, setupTestScript(t, "testdata/gno_test")) +func Test_ScriptsTest(t *testing.T) { + p := testscript.Params{ + Dir: "testdata/gno_test", + } + + if coverdir, ok := integration.ResolveCoverageDir(); ok { + err := integration.SetupTestscriptsCoverage(&p, coverdir) + require.NoError(t, err) + } + + err := integration.SetupGno(&p, t.TempDir()) + require.NoError(t, err) + + testscript.Run(t, p) } diff --git a/gnovm/cmd/gno/testdata/gno_build/empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_build/empty_dir.txtar new file mode 100644 index 00000000000..d346b6ad46f --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/empty_dir.txtar @@ -0,0 +1,6 @@ +# Run gno build on an empty dir + +gno build . + +! stdout .+ +! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_build/invalid_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_build/invalid_gno_files.txtar new file mode 100644 index 00000000000..617e12291be --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/invalid_gno_files.txtar @@ -0,0 +1,27 @@ +# Run gno build with invalid gno files (still success) + +gno build . + +! stdout .+ +! stderr .+ + +-- go.mod -- +module gnobuild + +-- file1.go -- +package file1 + +-- main.gno -- +package main + +invalid + +func main() {} + +-- sub/sub.gno -- +package sub + +invalid + +-- sub/file2.go -- +package file2 diff --git a/gnovm/cmd/gno/testdata/gno_build/invalid_go_files.txtar b/gnovm/cmd/gno/testdata/gno_build/invalid_go_files.txtar new file mode 100644 index 00000000000..a7c8b51af49 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/invalid_go_files.txtar @@ -0,0 +1,30 @@ +# Run gno build with invalid go files + +! gno build . + +! stdout .+ +stderr '\./file1\.go:3:1: syntax error: non-declaration statement outside function body' +stderr '\./\.: build pkg: std go compiler' +stderr 'sub/file2\.go:3:1: syntax error: non-declaration statement outside function body' +stderr '\./sub: build pkg: std go compiler' + +-- go.mod -- +module gnobuild + +-- file1.go -- +package file1 + +invalid1 + +-- main.gno -- +package main + +func main() {} + +-- sub/sub.gno -- +package sub + +-- sub/file2.go -- +package file2 + +invalid2 diff --git a/gnovm/cmd/gno/testdata/gno_build/no_args.txtar b/gnovm/cmd/gno/testdata/gno_build/no_args.txtar new file mode 100644 index 00000000000..b3f68676588 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/no_args.txtar @@ -0,0 +1,6 @@ +# Run gno build without args + +! gno build + +! stdout .+ +stderr 'flag: help requested' diff --git a/gnovm/cmd/gno/testdata/gno_build/no_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_build/no_gno_files.txtar new file mode 100644 index 00000000000..58261e77cda --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/no_gno_files.txtar @@ -0,0 +1,12 @@ +# Run gno build on a dir w/o gno files + +gno build . + +! stdout .+ +! stderr .+ + +-- README -- +Hello world + +-- sub/README -- +Hello world diff --git a/gnovm/cmd/gno/testdata/gno_build/no_go_files.txtar b/gnovm/cmd/gno/testdata/gno_build/no_go_files.txtar new file mode 100644 index 00000000000..46559610ccf --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/no_go_files.txtar @@ -0,0 +1,19 @@ +# Run gno build in a dir without go files + +! gno build . + +! stdout .+ +stderr -count=2 'no Go files in '$WORK +stderr '\./\.: build pkg: std go compiler' +stderr '\./sub: build pkg: std go compiler' + +-- go.mod -- +module gnobuild + +-- main.gno -- +package main + +func main() {} + +-- sub/sub.gno -- +package sub diff --git a/gnovm/cmd/gno/testdata/gno_build/no_gomod.txtar b/gnovm/cmd/gno/testdata/gno_build/no_gomod.txtar new file mode 100644 index 00000000000..9e6cad05664 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/no_gomod.txtar @@ -0,0 +1,16 @@ +# Run gno build on a dir without go.mod + +! gno build . + +! stdout .+ +stderr -count=2 'go: go.mod file not found in current directory or any parent directory' +stderr './.: build pkg: std go compiler' +stderr './sub: build pkg: std go compiler' + +-- main.gno -- +package main + +func main() {} + +-- sub/sub.gno -- +package sub diff --git a/gnovm/cmd/gno/testdata/gno_build/ok.txtar b/gnovm/cmd/gno/testdata/gno_build/ok.txtar new file mode 100644 index 00000000000..9d70fd97904 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_build/ok.txtar @@ -0,0 +1,23 @@ +# Run gno build successfully + +gno build . + +! stdout .+ +! stderr .+ + +-- go.mod -- +module gnobuild + +-- file1.go -- +package file1 + +-- main.gno -- +package main + +func main() {} + +-- sub/sub.gno -- +package sub + +-- sub/file2.go -- +package file2 diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar index 73f0da72dfe..ffed64ab9c7 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar @@ -2,5 +2,10 @@ gno test . +! stdout .+ +stderr '[no test files]' + +gno test ./... + ! stdout .+ stderr 'no packages to test' diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar index ae73b3ab275..cc673bb38ff 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar @@ -3,7 +3,7 @@ gno test . ! stdout .+ -stderr '\? \./\. \[no test files\]' +stderr '\? \. \[no test files\]' ! gno test --precompile . diff --git a/gnovm/cmd/gno/testdata/gno_test/error_correct.txtar b/gnovm/cmd/gno/testdata/gno_test/error_correct.txtar index c7d3187424c..a66d831b48c 100644 --- a/gnovm/cmd/gno/testdata/gno_test/error_correct.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/error_correct.txtar @@ -5,7 +5,7 @@ gno test -verbose . stdout 'Machine\.RunMain\(\) panic: oups' stderr '=== RUN file/x_filetest.gno' stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- x_filetest.gno -- package main diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar b/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar index bc3efc1a8c9..c739c1ce328 100644 --- a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar @@ -9,8 +9,8 @@ stderr 'panic: fail on failing_filetest.gno: got unexpected error: beep boop' ! gno test -verbose --precompile . stdout 'Machine.RunMain\(\) panic: beep boop' -stderr '=== PREC \./\.' -stderr '=== BUILD \./\.' +stderr '=== PREC \.' +stderr '=== BUILD \.' stderr '=== RUN file/failing_filetest.gno' stderr 'panic: fail on failing_filetest.gno: got unexpected error: beep boop' diff --git a/gnovm/cmd/gno/testdata/gno_test/minim1.txtar b/gnovm/cmd/gno/testdata/gno_test/minim1.txtar index 231ef4a270c..b0a77186086 100644 --- a/gnovm/cmd/gno/testdata/gno_test/minim1.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/minim1.txtar @@ -3,7 +3,7 @@ gno test . ! stdout .+ -stderr '\? \./\. \[no test files\]' +stderr '\? \. \[no test files\]' -- minim.gno -- package minim diff --git a/gnovm/cmd/gno/testdata/gno_test/minim2.txtar b/gnovm/cmd/gno/testdata/gno_test/minim2.txtar index 038dfd19289..3c4d1d085f0 100644 --- a/gnovm/cmd/gno/testdata/gno_test/minim2.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/minim2.txtar @@ -3,7 +3,7 @@ gno test . ! stdout .+ -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- minim.gno -- package minim diff --git a/gnovm/cmd/gno/testdata/gno_test/minim3.txtar b/gnovm/cmd/gno/testdata/gno_test/minim3.txtar index 8e657104801..ac8ae0c41d4 100644 --- a/gnovm/cmd/gno/testdata/gno_test/minim3.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/minim3.txtar @@ -3,7 +3,7 @@ gno test . ! stdout .+ -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- minim.gno -- package minim diff --git a/gnovm/cmd/gno/testdata/gno_test/output_correct.txtar b/gnovm/cmd/gno/testdata/gno_test/output_correct.txtar index ce12874f669..4e5495ab839 100644 --- a/gnovm/cmd/gno/testdata/gno_test/output_correct.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/output_correct.txtar @@ -5,7 +5,7 @@ gno test -verbose . ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- x_filetest.gno -- package main diff --git a/gnovm/cmd/gno/testdata/gno_test/output_sync.txtar b/gnovm/cmd/gno/testdata/gno_test/output_sync.txtar index 85fec4ab316..b21db788924 100644 --- a/gnovm/cmd/gno/testdata/gno_test/output_sync.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/output_sync.txtar @@ -5,7 +5,7 @@ gno test -verbose . -update-golden-tests ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' cmp x_filetest.gno x_filetest.gno.golden diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar index f376e61d9a4..c3d3b983e34 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar @@ -5,7 +5,7 @@ gno test -verbose . ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- x_filetest.gno -- // PKGPATH: gno.land/r/x diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar index 918fb0b88c9..236e69f8641 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar @@ -5,7 +5,7 @@ gno test -verbose . -update-golden-tests ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' cmp x_filetest.gno x_filetest.gno.golden diff --git a/gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar b/gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar index 51b5f323654..545607a9082 100644 --- a/gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar @@ -3,14 +3,14 @@ gno test . ! stdout .+ -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' gno test -verbose . ! stdout .+ stderr '=== RUN file/valid_filetest.gno' stderr '--- PASS: file/valid_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/testdata/gno_test/valid_test.txtar b/gnovm/cmd/gno/testdata/gno_test/valid_test.txtar index cb5f7286f60..9590626776c 100644 --- a/gnovm/cmd/gno/testdata/gno_test/valid_test.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/valid_test.txtar @@ -3,7 +3,12 @@ gno test . ! stdout .+ -stderr 'ok \./\. \d\.\d\ds' +stderr 'ok \. \d\.\d\ds' + +gno test ./... + +! stdout .+ +stderr 'ok \. \d\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 8288539c97b..73ee0f0323b 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "time" @@ -105,6 +106,89 @@ func gnoPackagesFromArgs(args []string) ([]string, error) { return paths, nil } +// targetsFromPatterns returns a list of target paths that match the patterns. +// Each pattern can represent a file or a directory, and if the pattern +// includes "/...", the "..." is treated as a wildcard, matching any string. +// Intended to be used by gno commands such as `gno test`. +func targetsFromPatterns(patterns []string) ([]string, error) { + paths := []string{} + for _, p := range patterns { + var match func(string) bool + patternLookup := false + dirToSearch := p + + // Check if the pattern includes `/...` + if strings.Contains(p, "/...") { + index := strings.Index(p, "/...") + if index != -1 { + dirToSearch = p[:index] // Extract the directory path to search + } + match = matchPattern(strings.TrimPrefix(p, "./")) + patternLookup = true + } + + info, err := os.Stat(dirToSearch) + if err != nil { + return nil, fmt.Errorf("invalid file or package path: %w", err) + } + + // If the pattern is a file or a directory + // without `/...`, add it to the list. + if !info.IsDir() || !patternLookup { + paths = append(paths, p) + continue + } + + // the pattern is a dir containing `/...`, walk the dir recursively and + // look for directories containing at least one .gno file and match pattern. + visited := map[string]bool{} // used to run the builder only once per folder. + err = filepath.WalkDir(dirToSearch, func(curpath string, f fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("%s: walk dir: %w", dirToSearch, err) + } + // Skip directories and non ".gno" files. + if f.IsDir() || !isGnoFile(f) { + return nil + } + + parentDir := filepath.Dir(curpath) + if _, found := visited[parentDir]; found { + return nil + } + + visited[parentDir] = true + if match(parentDir) { + paths = append(paths, parentDir) + } + + return nil + }) + if err != nil { + return nil, err + } + } + return paths, nil +} + +// matchPattern(pattern)(name) reports whether +// name matches pattern. Pattern is a limited glob +// pattern in which '...' means 'any string' and there +// is no other special syntax. +// Simplified version of go source's matchPatternInternal +// (see $GOROOT/src/cmd/internal/pkgpattern) +func matchPattern(pattern string) func(name string) bool { + re := regexp.QuoteMeta(pattern) + re = strings.Replace(re, `\.\.\.`, `.*`, -1) + // Special case: foo/... matches foo too. + if strings.HasSuffix(re, `/.*`) { + re = re[:len(re)-len(`/.*`)] + `(/.*)?` + } + reg := regexp.MustCompile(`^` + re + `$`) + return func(name string) bool { + return reg.MatchString(name) + } +} + func fmtDuration(d time.Duration) string { return fmt.Sprintf("%.2fs", d.Seconds()) } diff --git a/gnovm/cmd/gno/util_test.go b/gnovm/cmd/gno/util_test.go new file mode 100644 index 00000000000..9e9659bfe4f --- /dev/null +++ b/gnovm/cmd/gno/util_test.go @@ -0,0 +1,297 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMatchPattern(t *testing.T) { + tests := []struct { + pattern string + names []string + expected []bool + }{ + { + pattern: "foo", + names: []string{"foo", "bar", "baz", "foo/bar"}, + expected: []bool{true, false, false, false}, + }, + { + pattern: "foo/...", + names: []string{"foo", "foo/bar", "foo/bar/baz", "bar", "baz"}, + expected: []bool{true, true, true, false, false}, + }, + { + pattern: "foo/bar/...", + names: []string{"foo/bar", "foo/bar/baz", "foo/baz/bar", "foo", "bar"}, + expected: []bool{true, true, false, false, false}, + }, + { + pattern: "foo/.../baz", + names: []string{"foo/bar", "foo/bar/baz", "foo/baz/bar", "foo", "bar"}, + expected: []bool{false, true, false, false, false}, + }, + { + pattern: "foo/.../baz/...", + names: []string{"foo/bar/baz", "foo/baz/bar", "foo/bar/baz/qux", "foo/baz/bar/qux"}, + expected: []bool{true, false, true, false}, + }, + { + pattern: "...", + names: []string{"foo", "bar", "baz", "foo/bar", "foo/bar/baz"}, + expected: []bool{true, true, true, true, true}, + }, + { + pattern: ".../bar", + names: []string{"foo", "bar", "baz", "foo/bar", "foo/bar/baz"}, + expected: []bool{false, false, false, true, false}, + }, + } + + for _, test := range tests { + t.Run(test.pattern, func(t *testing.T) { + matchFunc := matchPattern(test.pattern) + for i, name := range test.names { + res := matchFunc(name) + assert.Equal(t, test.expected[i], res, "Expected: %v, Got: %v", test.expected[i], res) + } + }) + } +} + +func TestTargetsFromPatterns(t *testing.T) { + tmpDir := t.TempDir() + createGnoPackages(t, tmpDir) + + for _, tc := range []struct { + desc string + in, expected []string + errorShouldContain string + }{ + { + desc: "valid1", + in: []string{ + tmpDir, + }, + expected: []string{ + tmpDir, + }, + }, + { + desc: "valid2", + in: []string{ + tmpDir + "/foo", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + }, + }, + { + desc: "valid_recursive1", + in: []string{ + tmpDir + "/...", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + filepath.Join(tmpDir, "bar"), + filepath.Join(tmpDir, "baz"), + filepath.Join(tmpDir, "foo", "qux"), + filepath.Join(tmpDir, "bar", "quux"), + filepath.Join(tmpDir, "foo", "qux", "corge"), + }, + }, + { + desc: "valid_recursive2", + in: []string{ + tmpDir + "/foo/...", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + filepath.Join(tmpDir, "foo", "qux"), + filepath.Join(tmpDir, "foo", "qux", "corge"), + }, + }, + { + desc: "valid_recursive2", + in: []string{ + tmpDir + "/.../qux", + }, + expected: []string{ + filepath.Join(tmpDir, "foo", "qux"), + }, + }, + { + desc: "valid_recursive3", + in: []string{ + tmpDir + "/.../qux/...", + }, + expected: []string{ + filepath.Join(tmpDir, "foo", "qux"), + filepath.Join(tmpDir, "foo", "qux", "corge"), + }, + }, + { + desc: "multiple_input", + in: []string{ + tmpDir + "/foo", + tmpDir + "/bar", + tmpDir + "/baz", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + filepath.Join(tmpDir, "bar"), + filepath.Join(tmpDir, "baz"), + }, + }, + { + desc: "mixed_input1", + in: []string{ + tmpDir + "/foo", + tmpDir + "/bar/...", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + filepath.Join(tmpDir, "bar"), + filepath.Join(tmpDir, "bar", "quux"), + }, + }, + { + desc: "mixed_input2", + in: []string{ + tmpDir + "/foo", + tmpDir + "/bar/...", + tmpDir + "/baz/baz.gno", + }, + expected: []string{ + filepath.Join(tmpDir, "foo"), + filepath.Join(tmpDir, "bar"), + filepath.Join(tmpDir, "bar", "quux"), + filepath.Join(tmpDir, "baz", "baz.gno"), + }, + }, + { + desc: "not_exists1", + in: []string{ + tmpDir + "/notexists", // dir path + }, + errorShouldContain: "no such file or directory", + }, + { + desc: "not_exists2", + in: []string{ + tmpDir + "/foo/bar.gno", // file path + }, + errorShouldContain: "no such file or directory", + }, + { + desc: "not_exists3", // mixed + in: []string{ + tmpDir + "/foo", // exists + tmpDir + "/notexists", // not exists + }, + errorShouldContain: "no such file or directory", + }, + } { + t.Run(tc.desc, func(t *testing.T) { + targets, err := targetsFromPatterns(tc.in) + if tc.errorShouldContain != "" { + assert.ErrorContains(t, err, tc.errorShouldContain) + return + } + assert.NoError(t, err) + require.Equal(t, len(tc.expected), len(targets)) + for _, tr := range targets { + assert.Contains(t, tc.expected, tr) + } + }) + } +} + +func createGnoPackages(t *testing.T, tmpDir string) { + t.Helper() + + type file struct { + name, data string + } + // Gno pkgs to create + pkgs := []struct { + dir string + files []file + }{ + // pkg 'foo', 'bar' and 'baz' + { + dir: filepath.Join(tmpDir, "foo"), + files: []file{ + { + name: "foo.gno", + data: `package foo`, + }, + }, + }, + { + dir: filepath.Join(tmpDir, "bar"), + files: []file{ + { + name: "bar.gno", + data: `package bar`, + }, + }, + }, + { + dir: filepath.Join(tmpDir, "baz"), + files: []file{ + { + name: "baz.gno", + data: `package baz`, + }, + }, + }, + + // pkg inside 'foo' pkg + { + dir: filepath.Join(tmpDir, "foo", "qux"), + files: []file{ + { + name: "qux.gno", + data: `package qux`, + }, + }, + }, + + // pkg inside 'bar' pkg + { + dir: filepath.Join(tmpDir, "bar", "quux"), + files: []file{ + { + name: "quux.gno", + data: `package quux`, + }, + }, + }, + + // pkg inside 'foo/qux' pkg + { + dir: filepath.Join(tmpDir, "foo", "qux", "corge"), + files: []file{ + { + name: "corge.gno", + data: `package corge`, + }, + }, + }, + } + + // Create pkgs + for _, p := range pkgs { + err := os.MkdirAll(p.dir, 0o700) + require.NoError(t, err) + for _, f := range p.files { + err = os.WriteFile(filepath.Join(p.dir, f.name), []byte(f.data), 0o644) + require.NoError(t, err) + } + } +} diff --git a/gnovm/docs/go-gno-compatibility.md b/gnovm/docs/go-gno-compatibility.md deleted file mode 100644 index 98f42aa9f29..00000000000 --- a/gnovm/docs/go-gno-compatibility.md +++ /dev/null @@ -1,373 +0,0 @@ -# Go<>Gno compatibility - -**WIP: does not reflect the current state yet.** - -## Native keywords - -Legend: full, partial, missing, TBD. - -| keyword | support | -|-------------|------------------------| -| break | full | -| case | full | -| const | full | -| continue | full | -| default | full | -| defer | full | -| else | full | -| fallthrough | full | -| for | full | -| func | full | -| go | missing (after launch) | -| goto | full | -| if | full | -| import | full | -| interface | full | -| package | full | -| range | full | -| return | full | -| select | missing (after launch) | -| struct | full | -| switch | full | -| type | full | -| var | full | - -## Native types - -| type | usage | persistency | -|-----------------------------------------------|------------------------|------------------------------------------------------------| -| `bool` | full | full | -| `byte` | full | full | -| `float32`, `float64` | full | full | -| `int`, `int8`, `int16`, `int32`, `int64` | full | full | -| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | full | full | -| `string` | full | full | -| `rune` | full | full | -| `interface{}` | full | full | -| `[]T` (slices) | full | full* | -| `map[T1]T2` | full | full* | -| `func (T1...) T2...` | full | full (needs more tests) | -| `*T` (pointers) | full | full* | -| `chan T` (channels) | missing (after launch) | missing (after launch) | - -**\*:** depends on `T`/`T1`/`T2` - -Additional native types: - -| type | comment | -|----------|--------------------------------------------------------------------------------------------| -| `bigint` | Based on `math/big.Int` | -| `bigdec` | Based on https://github.com/cockroachdb/apd, (see https://github.com/gnolang/gno/pull/306) | - - -## Stdlibs - - - -| package | status | -|---------------------------------------------|----------| -| archive/tar | TBD | -| archive/zip | TBD | -| arena | TBD | -| bufio | TBD | -| builtin | TBD | -| bytes | TBD | -| cmd/addr2line | TBD | -| cmd/api | TBD | -| cmd/api/testdata/src/issue21181/dep | TBD | -| cmd/api/testdata/src/issue21181/indirect | TBD | -| cmd/api/testdata/src/issue21181/p | TBD | -| cmd/api/testdata/src/pkg/p1 | TBD | -| cmd/api/testdata/src/pkg/p2 | TBD | -| cmd/api/testdata/src/pkg/p3 | TBD | -| cmd/api/testdata/src/pkg/p4 | TBD | -| cmd/asm | TBD | -| cmd/buildid | TBD | -| cmd/cgo | TBD | -| cmd/compile | TBD | -| cmd/covdata | TBD | -| cmd/covdata/testdata | TBD | -| cmd/cover | TBD | -| cmd/cover/testdata | TBD | -| cmd/cover/testdata/html | TBD | -| cmd/cover/testdata/pkgcfg/a | TBD | -| cmd/cover/testdata/pkgcfg/b | TBD | -| cmd/cover/testdata/pkgcfg/main | TBD | -| cmd/dist | TBD | -| cmd/distpack | TBD | -| cmd/doc | TBD | -| cmd/doc/testdata | TBD | -| cmd/doc/testdata/merge | TBD | -| cmd/doc/testdata/nested | TBD | -| cmd/doc/testdata/nested/empty | TBD | -| cmd/doc/testdata/nested/nested | TBD | -| cmd/fix | TBD | -| cmd/go | TBD | -| cmd/gofmt | TBD | -| cmd/go/testdata | TBD | -| cmd/link | TBD | -| cmd/link/testdata/pe-binutils | TBD | -| cmd/link/testdata/pe-llvm | TBD | -| cmd/link/testdata/testBuildFortvOS | TBD | -| cmd/link/testdata/testHashedSyms | TBD | -| cmd/link/testdata/testIndexMismatch | TBD | -| cmd/link/testdata/testRO | TBD | -| cmd/nm | TBD | -| cmd/objdump | TBD | -| cmd/objdump/testdata | TBD | -| cmd/objdump/testdata/testfilenum | TBD | -| cmd/pack | TBD | -| cmd/pprof | TBD | -| cmd/pprof/testdata | TBD | -| cmd/test2json | TBD | -| cmd/trace | TBD | -| cmd/vet | TBD | -| cmd/vet/testdata/asm | TBD | -| cmd/vet/testdata/assign | TBD | -| cmd/vet/testdata/atomic | TBD | -| cmd/vet/testdata/bool | TBD | -| cmd/vet/testdata/buildtag | TBD | -| cmd/vet/testdata/cgo | TBD | -| cmd/vet/testdata/composite | TBD | -| cmd/vet/testdata/copylock | TBD | -| cmd/vet/testdata/deadcode | TBD | -| cmd/vet/testdata/directive | TBD | -| cmd/vet/testdata/httpresponse | TBD | -| cmd/vet/testdata/lostcancel | TBD | -| cmd/vet/testdata/method | TBD | -| cmd/vet/testdata/nilfunc | TBD | -| cmd/vet/testdata/print | TBD | -| cmd/vet/testdata/rangeloop | TBD | -| cmd/vet/testdata/shift | TBD | -| cmd/vet/testdata/structtag | TBD | -| cmd/vet/testdata/tagtest | TBD | -| cmd/vet/testdata/testingpkg | TBD | -| cmd/vet/testdata/unmarshal | TBD | -| cmd/vet/testdata/unsafeptr | TBD | -| cmd/vet/testdata/unused | TBD | -| compress/bzip2 | TBD | -| compress/flate | TBD | -| compress/gzip | TBD | -| compress/lzw | TBD | -| compress/zlib | TBD | -| container/heap | TBD | -| container/list | TBD | -| container/ring | TBD | -| context | TBD | -| crypto | TBD | -| crypto/aes | TBD | -| crypto/boring | TBD | -| crypto/cipher | TBD | -| crypto/des | TBD | -| crypto/dsa | TBD | -| crypto/ecdh | TBD | -| crypto/ecdsa | TBD | -| crypto/ed25519 | TBD | -| crypto/elliptic | TBD | -| crypto/hmac | TBD | -| crypto/md5 | TBD | -| crypto/rand | TBD | -| crypto/rc4 | TBD | -| crypto/rsa | TBD | -| crypto/sha1 | TBD | -| crypto/sha256 | TBD | -| crypto/sha512 | TBD | -| crypto/subtle | TBD | -| crypto/tls | TBD | -| crypto/tls/fipsonly | TBD | -| crypto/x509 | TBD | -| crypto/x509/pkix | TBD | -| database/sql | TBD | -| database/sql/driver | TBD | -| debug/buildinfo | TBD | -| debug/dwarf | TBD | -| debug/elf | TBD | -| debug/gosym | TBD | -| debug/gosym/testdata | TBD | -| debug/macho | TBD | -| debug/pe | TBD | -| debug/plan9obj | TBD | -| embed | TBD | -| encoding | TBD | -| encoding/ascii85 | TBD | -| encoding/asn1 | TBD | -| encoding/base32 | TBD | -| encoding/base64 | TBD | -| encoding/binary | partial | -| encoding/csv | TBD | -| encoding/gob | TBD | -| encoding/hex | TBD | -| encoding/json | TBD | -| encoding/pem | TBD | -| encoding/xml | TBD | -| errors | TBD | -| expvar | TBD | -| flag | TBD | -| fmt | TBD | -| go/ast | TBD | -| go/build | TBD | -| go/build/constraint | TBD | -| go/build/testdata/alltags | TBD | -| go/build/testdata/cgo_disabled | TBD | -| go/build/testdata/directives | TBD | -| go/build/testdata/doc | TBD | -| go/build/testdata/multi | TBD | -| go/build/testdata/non_source_tags | TBD | -| go/build/testdata/other | TBD | -| go/build/testdata/other/file | TBD | -| go/constant | TBD | -| go/doc | TBD | -| go/doc/comment | TBD | -| go/doc/testdata | TBD | -| go/doc/testdata/examples | TBD | -| go/doc/testdata/pkgdoc | TBD | -| go/format | TBD | -| go/importer | TBD | -| go/parser | TBD | -| go/parser/testdata/goversion | TBD | -| go/parser/testdata/issue42951 | TBD | -| go/parser/testdata/issue42951/not_a_file.go | TBD | -| go/printer | TBD | -| go/printer/testdata | TBD | -| go/scanner | TBD | -| go/token | TBD | -| go/types | TBD | -| go/types/testdata | TBD | -| go/types/testdata/local | TBD | -| hash | TBD | -| hash/adler32 | TBD | -| hash/crc32 | TBD | -| hash/crc64 | TBD | -| hash/fnv | TBD | -| hash/maphash | TBD | -| html | TBD | -| html/template | TBD | -| image | TBD | -| image/color | TBD | -| image/color/palette | TBD | -| image/draw | TBD | -| image/gif | TBD | -| image/jpeg | TBD | -| image/png | TBD | -| index/suffixarray | TBD | -| io | TBD | -| io/fs | TBD | -| io/ioutil | TBD | -| log | TBD | -| log/internal | TBD | -| log/slog | TBD | -| log/slog/internal | TBD | -| log/syslog | TBD | -| maps | TBD | -| math | partial | -| math/big | TBD | -| math/bits | TBD | -| math/cmplx | TBD | -| math/rand | TBD | -| mime | TBD | -| mime/multipart | TBD | -| mime/quotedprintable | TBD | -| net | TBD | -| net/http | TBD | -| net/http/cgi | TBD | -| net/http/cookiejar | TBD | -| net/http/fcgi | TBD | -| net/http/httptest | TBD | -| net/http/httptrace | TBD | -| net/http/httputil | TBD | -| net/http/internal | TBD | -| net/http/pprof | TBD | -| net/mail | TBD | -| net/netip | TBD | -| net/rpc | TBD | -| net/rpc/jsonrpc | TBD | -| net/smtp | TBD | -| net/textproto | TBD | -| net/url | full | -| os | TBD | -| os/exec | TBD | -| os/signal | TBD | -| os/user | TBD | -| path | full | -| path/filepath | TBD | -| plugin | TBD | -| reflect | TBD | -| regexp | TBD | -| regexp/syntax | TBD | -| runtime | TBD | -| runtime/asan | TBD | -| runtime/cgo | TBD | -| runtime/coverage | TBD | -| runtime/coverage/testdata | TBD | -| runtime/coverage/testdata/issue56006 | TBD | -| runtime/debug | TBD | -| runtime/metrics | TBD | -| runtime/msan | TBD | -| runtime/pprof | TBD | -| runtime/pprof/testdata/mappingtest | TBD | -| runtime/race | TBD | -| runtime/race/testdata | TBD | -| runtime/testdata/testexithooks | TBD | -| runtime/testdata/testfaketime | TBD | -| runtime/testdata/testprog | TBD | -| runtime/testdata/testprogcgo | TBD | -| runtime/testdata/testprogcgo/windows | TBD | -| runtime/testdata/testprognet | TBD | -| runtime/testdata/testwinlib | TBD | -| runtime/testdata/testwinlibsignal | TBD | -| runtime/testdata/testwinlibthrow | TBD | -| runtime/testdata/testwinsignal | TBD | -| runtime/trace | TBD | -| slices | TBD | -| sort | TBD | -| strconv | TBD | -| strings | TBD | -| sync | TBD | -| sync/atomic | TBD | -| syscall | TBD | -| syscall/js | TBD | -| testing | TBD | -| testing/fstest | TBD | -| testing/iotest | TBD | -| testing/quick | TBD | -| text/scanner | TBD | -| text/tabwriter | TBD | -| text/template | TBD | -| text/template/parse | TBD | -| time | TBD | -| time/tzdata | TBD | -| unicode | TBD | -| unicode/utf16 | TBD | -| unicode/utf8 | TBD | -| unsafe | TBD | - - - -## Tooling (`gno` binary) - -| go command | gno command | comment | -|-------------------|------------------|-----------------------------------------------------------------------| -| go bug | | see https://github.com/gnolang/gno/issues/733 | -| go build | gno build | same intention, limited compatibility | -| go clean | gno clean | same intention, limited compatibility | -| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 | -| go env | | | -| go fix | | | -| go fmt | | | -| go generate | | | -| go get | | | -| go help | | | -| go install | | | -| go list | | | -| go mod | | | -| + go mod download | gno mod download | same behavior | -| + go mod init | gno mod init | same behavior | -| | gno precompile | | -| go work | | | -| | gno repl | | -| go run | gno run | | -| go test | gno test | limited compatibility | -| go tool | | | -| go version | | | -| go vet | | | -| golint | gno lint | same intention | diff --git a/gnovm/pkg/doc/dirs.go b/gnovm/pkg/doc/dirs.go index 21216828ce4..19d312f6826 100644 --- a/gnovm/pkg/doc/dirs.go +++ b/gnovm/pkg/doc/dirs.go @@ -5,7 +5,6 @@ package doc import ( - "fmt" "log" "os" "path" @@ -52,7 +51,7 @@ func newDirs(dirs []string, modDirs []string) *bfsDirs { } for _, mdir := range modDirs { - gm, err := parseGnoMod(filepath.Join(mdir, "gno.mod")) + gm, err := gnomod.ParseGnoMod(filepath.Join(mdir, "gno.mod")) if err != nil { log.Printf("%v", err) continue @@ -68,34 +67,6 @@ func newDirs(dirs []string, modDirs []string) *bfsDirs { return d } -// tries to parse gno mod file given the filename, using Parse and Validate from -// the gnomod package -// -// TODO(tb): replace by `gnomod.ParseAt` ? The key difference is the latter -// looks for gno.mod in parent directories, while this function doesn't. -func parseGnoMod(fname string) (*gnomod.File, error) { - file, err := os.Stat(fname) - if err != nil { - return nil, fmt.Errorf("could not read gno.mod file: %w", err) - } - if file.IsDir() { - return nil, fmt.Errorf("invalid gno.mod at %q: is a directory", fname) - } - - b, err := os.ReadFile(fname) - if err != nil { - return nil, fmt.Errorf("could not read gno.mod file: %w", err) - } - gm, err := gnomod.Parse(fname, b) - if err != nil { - return nil, fmt.Errorf("error parsing gno.mod file at %q: %w", fname, err) - } - if err := gm.Validate(); err != nil { - return nil, fmt.Errorf("error validating gno.mod file at %q: %w", fname, err) - } - return gm, nil -} - func getGnoModDirs(gm *gnomod.File) []bfsDir { // cmd/go makes use of the go list command, we don't have that here. diff --git a/gnovm/pkg/gnolang/alloc_test.go b/gnovm/pkg/gnolang/alloc_test.go index e3b9f1cb069..dbb3903f862 100644 --- a/gnovm/pkg/gnolang/alloc_test.go +++ b/gnovm/pkg/gnolang/alloc_test.go @@ -6,6 +6,8 @@ import ( ) func TestAllocSizes(t *testing.T) { + t.Parallel() + // go elemental println("_allocPointer", unsafe.Sizeof(&StructValue{})) println("_allocSlice", unsafe.Sizeof([]byte("12345678901234567890123456789012345678901234567890"))) diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index 5ed5971c836..37b590b1c4c 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -13,6 +13,8 @@ import ( // run empty main(). func TestRunEmptyMain(t *testing.T) { + t.Parallel() + m := NewMachine("test", nil) main := FuncD("main", nil, nil, nil) m.RunDeclaration(main) @@ -21,6 +23,8 @@ func TestRunEmptyMain(t *testing.T) { // run main() with a for loop. func TestRunLoopyMain(t *testing.T) { + t.Parallel() + m := NewMachine("test", nil) c := `package test func main() { @@ -36,6 +40,8 @@ func main() { } func TestEval(t *testing.T) { + t.Parallel() + m := NewMachine("test", nil) c := `package test func next(i int) int { @@ -64,6 +70,8 @@ func assertOutput(t *testing.T, input string, output string) { } func TestRunMakeStruct(t *testing.T) { + t.Parallel() + assertOutput(t, `package test type Outfit struct { Scarf string @@ -90,6 +98,8 @@ func main() { } func TestRunReturnStruct(t *testing.T) { + t.Parallel() + assertOutput(t, `package test type MyStruct struct { FieldA string @@ -160,6 +170,8 @@ type Struct1 struct { } func TestModifyTypeAsserted(t *testing.T) { + t.Parallel() + x := Struct1{1, 1} var v interface{} = x x2 := v.(Struct1) @@ -176,6 +188,8 @@ type Interface1 interface { } func TestTypeConversion(t *testing.T) { + t.Parallel() + x := 1 var v interface{} = x if _, ok := v.(Interface1); ok { @@ -193,6 +207,8 @@ func TestTypeConversion(t *testing.T) { } func TestSomething(t *testing.T) { + t.Parallel() + type Foo struct { X interface{} } @@ -210,6 +226,8 @@ func TestSomething(t *testing.T) { // XXX is there a way to test in Go as well as Gno? func TestDeferOrder(t *testing.T) { + t.Parallel() + a := func() func(int, int) int { fmt.Println("a constructed") return func(x int, y int) int { @@ -238,6 +256,8 @@ func TestDeferOrder(t *testing.T) { // XXX is there a way to test in Go as well as Gno? func TestCallOrder(t *testing.T) { + t.Parallel() + a := func() func(int, int) int { fmt.Println("a constructed") return func(x int, y int) int { @@ -264,6 +284,8 @@ func TestCallOrder(t *testing.T) { // XXX is there a way to test in Go as well as Gno? func TestBinaryShortCircuit(t *testing.T) { + t.Parallel() + tr := func() bool { fmt.Println("t called") return true @@ -281,6 +303,8 @@ func TestBinaryShortCircuit(t *testing.T) { // XXX is there a way to test in Go as well as Gno? func TestSwitchDefine(t *testing.T) { + t.Parallel() + var x interface{} = 1 switch y := x.(type) { case int: @@ -292,6 +316,8 @@ func TestSwitchDefine(t *testing.T) { // XXX is there a way to test in Go as well as Gno? func TestBinaryCircuit(t *testing.T) { + t.Parallel() + tr := func() bool { fmt.Println("tr() called") return true @@ -316,6 +342,8 @@ func TestBinaryCircuit(t *testing.T) { } func TestMultiAssignment(t *testing.T) { + t.Parallel() + buf := make([]int, 4) ref := func(i int) *int { fmt.Printf("ref(%v) called\n", i) @@ -342,6 +370,8 @@ func TestMultiAssignment(t *testing.T) { // XXX is there a way to test in Go as well as Gno? func TestCallLHS(t *testing.T) { + t.Parallel() + x := 1 xptr := func() *int { return &x @@ -352,6 +382,8 @@ func TestCallLHS(t *testing.T) { // XXX is there a way to test in Go as well as Gno? func TestCallFieldLHS(t *testing.T) { + t.Parallel() + type str struct { X int } diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 6a1413a07e1..d5b94c618b0 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -8,6 +8,8 @@ import ( ) func TestParseForLoop(t *testing.T) { + t.Parallel() + gocode := `package main func main(){ for i:=0; i<10; i++ { diff --git a/gnovm/pkg/gnolang/gonative_test.go b/gnovm/pkg/gnolang/gonative_test.go index b9b659aae99..e348ffe9c22 100644 --- a/gnovm/pkg/gnolang/gonative_test.go +++ b/gnovm/pkg/gnolang/gonative_test.go @@ -98,6 +98,8 @@ D: } func TestGoNativeDefine3(t *testing.T) { + t.Parallel() + // Create package foo and define Foo. out := new(bytes.Buffer) pkg := NewPackageNode("foo", "test.foo", nil) @@ -135,6 +137,8 @@ D: } func TestCrypto(t *testing.T) { + t.Parallel() + addr := crypto.Address{} store := gonativeTestStore() tv := Go2GnoValue(nilAllocator, store, reflect.ValueOf(addr)) diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go index c16ea795ea3..4810a67304a 100644 --- a/gnovm/pkg/gnolang/helpers.go +++ b/gnovm/pkg/gnolang/helpers.go @@ -440,18 +440,6 @@ var ( precs = [][]string{prec1, prec2, prec3, prec4, prec5} ) -// 0 for prec1... -1 if no match. -func lowestMatch(op string) int { - for i, prec := range precs { - for _, op2 := range prec { - if op == op2 { - return i - } - } - } - return -1 -} - func Ss(b ...Stmt) []Stmt { return b } diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 46308fb3a02..2a9e0b51a97 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -4,7 +4,6 @@ import ( "fmt" "go/parser" "go/token" - "io/ioutil" "os" "path/filepath" "reflect" @@ -1095,7 +1094,7 @@ func PackageNameFromFileBody(name, body string) Name { // NOTE: panics if package name is invalid. func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) if err != nil { panic(err) } diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index f43a593c50e..8d652667111 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -154,7 +154,11 @@ func (m *Machine) doOpCall() { } // TODO: some more pt <> pv.Type // reconciliations/conversions necessary. - b.Values[i] = pv + + // Make a copy so that a reference to the arguemnt isn't used + // in cases where the non-primitive value type is represented + // as a pointer, *StructValue, for example. + b.Values[i] = pv.Copy(m.Alloc) } } diff --git a/gnovm/pkg/gnolang/package_test.go b/gnovm/pkg/gnolang/package_test.go index 47efd736bac..f219ba9b23f 100644 --- a/gnovm/pkg/gnolang/package_test.go +++ b/gnovm/pkg/gnolang/package_test.go @@ -12,6 +12,8 @@ import ( // Tries to reproduce the bug #1036 on all registered types func TestAminoJSONRegisteredTypes(t *testing.T) { + t.Parallel() + for _, typ := range Package.Types { // Instantiate registered type x := reflect.New(typ.Type).Interface() diff --git a/gnovm/pkg/gnolang/precompile.go b/gnovm/pkg/gnolang/precompile.go index f7a10d5589a..c3116f25800 100644 --- a/gnovm/pkg/gnolang/precompile.go +++ b/gnovm/pkg/gnolang/precompile.go @@ -7,7 +7,6 @@ import ( "go/format" "go/parser" "go/token" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -112,7 +111,7 @@ func GetPrecompileFilenameAndTags(gnoFilePath string) (targetFilename, tags stri func PrecompileAndCheckMempkg(mempkg *std.MemPackage) error { gofmt := "gofmt" - tmpDir, err := ioutil.TempDir("", mempkg.Name) + tmpDir, err := os.MkdirTemp("", mempkg.Name) if err != nil { return err } diff --git a/gnovm/pkg/gnolang/precompile_test.go b/gnovm/pkg/gnolang/precompile_test.go index 3d50353505f..e2cb5e92d86 100644 --- a/gnovm/pkg/gnolang/precompile_test.go +++ b/gnovm/pkg/gnolang/precompile_test.go @@ -12,6 +12,8 @@ import ( ) func TestPrecompile(t *testing.T) { + t.Parallel() + cases := []struct { name string source string @@ -56,6 +58,8 @@ func TestPrecompile(t *testing.T) { for _, c := range cases { c := c // scopelint t.Run(c.name, func(t *testing.T) { + t.Parallel() + // parse gno fset := token.NewFileSet() f, err := parser.ParseFile(fset, "foo.go", c.source, parser.ParseComments) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 30493a9e1c9..4cb8f597a8b 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -3350,23 +3350,6 @@ func elideCompositeExpr(vx *Expr, vt Type) { } } -// returns true of x is exactly `nil`. -func isNilExpr(x Expr) bool { - if nx, ok := x.(*NameExpr); ok { - return nx.Name == nilStr - } - return false -} - -func isNilComparableKind(k Kind) bool { - switch k { - case SliceKind, MapKind, FuncKind: - return true - default: - return false - } -} - // returns number of args, or if arg is a call result, // the number of results of the return tuple type. func countNumArgs(store Store, last BlockNode, n *CallExpr) (numArgs int) { @@ -3386,13 +3369,6 @@ func countNumArgs(store Store, last BlockNode, n *CallExpr) (numArgs int) { } } -func mergeNames(a, b []Name) []Name { - c := make([]Name, len(a)+len(b)) - copy(c, a) - copy(c[len(a):], b) - return c -} - // This is to be run *after* preprocessing is done, // to determine the order of var decl execution // (which may include functions which may refer to package vars). diff --git a/gnovm/pkg/gnolang/scanner.go b/gnovm/pkg/gnolang/scanner.go index 87695c0f346..f0b3f34c9ad 100644 --- a/gnovm/pkg/gnolang/scanner.go +++ b/gnovm/pkg/gnolang/scanner.go @@ -192,21 +192,3 @@ func (ss *scanner) advanceEscapeSequence() bool { return ss.done() } } - -// pops the next monoid term. -// The result is a string enclosed in balanced parentheses, -// brackets, or quotes; or what comes before such things. -// scanner doesn't understand operators, so a polynomial -// expression could be a single monoid as far as this scanner -// is concerned. TODO Chop functions should maybe use this. -func (ss *scanner) popMonoid() string { - startOut := ss.out() - start := ss.idx - for !ss.advance() { - if ss.out() != startOut { - end := ss.idx - return string(ss.rnz[start:end]) - } - } - panic("no monoid") -} diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index d3628edf216..24aff4936f3 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -11,8 +11,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/store" ) -const iavlCacheSize = 1024 * 1024 // TODO increase and parameterize. - // return nil if package doesn't exist. type PackageGetter func(pkgPath string) (*PackageNode, *PackageValue) @@ -626,7 +624,7 @@ func (ds *defaultStore) Flush() { // XXX } -//---------------------------------------- +// ---------------------------------------- // StoreOp type StoreOpType uint8 @@ -723,7 +721,7 @@ func (ds *defaultStore) Print() { } } -//---------------------------------------- +// ---------------------------------------- // backend keys func backendObjectKey(oid ObjectID) string { @@ -755,7 +753,7 @@ func backendPackagePathKey(path string) string { return fmt.Sprintf("pkg:" + path) } -//---------------------------------------- +// ---------------------------------------- // builtin types and packages func InitStoreCaches(store Store) { diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index c3e439e9427..6aed71fcf9b 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -879,7 +879,6 @@ type InterfaceType struct { } // General empty interface. -var gEmptyInterfaceType *InterfaceType = &InterfaceType{} func (it *InterfaceType) IsEmptyInterface() bool { return len(it.Methods) == 0 diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 3de74ac0130..3bdd3332e08 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -500,7 +500,15 @@ func (sv *StructValue) Copy(alloc *Allocator) *StructValue { } */ fields := alloc.NewStructFields(len(sv.Fields)) - copy(fields, sv.Fields) + + // Each field needs to be copied individually to ensure that + // value fields are copied as such, even though they may be represented + // as pointers. A good example of this would be a struct that has + // a field that is an array. The value array is represented as a pointer. + for i, field := range sv.Fields { + fields[i] = field.Copy(alloc) + } + return alloc.NewStruct(fields) } diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index 9fc2ce4a567..cc7c0de9f09 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -1240,12 +1240,14 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { case Float32Kind: dst.T = t dst.V = nil - f64, _ := bd.Float64() + f64, err := bd.Float64() + if err != nil { + panic(fmt.Errorf("cannot convert untyped bigdec to float64: %w", err)) + } + bf := big.NewFloat(f64) - f32, acc := bf.Float32() - if f32 == 0 && (acc == big.Below || acc == big.Above) { - panic("cannot convert untyped bigdec to float32 -- too close to zero") - } else if math.IsInf(float64(f32), 0) { + f32, _ := bf.Float32() + if math.IsInf(float64(f32), 0) { panic("cannot convert untyped bigdec to float32 -- too close to +-Inf") } dst.SetFloat32(f32) @@ -1253,10 +1255,11 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { case Float64Kind: dst.T = t dst.V = nil - f64, _ := bd.Float64() - if f64 == 0 && !bd.IsZero() { - panic("cannot convert untyped bigdec to float64 -- too close to zero") - } else if math.IsInf(f64, 0) { + f64, err := bd.Float64() + if err != nil { + panic(fmt.Errorf("cannot convert untyped bigdec to float64: %w", err)) + } + if math.IsInf(f64, 0) { panic("cannot convert untyped bigdec to float64 -- too close to +-Inf") } dst.SetFloat64(f64) diff --git a/gnovm/pkg/gnolang/values_conversions_test.go b/gnovm/pkg/gnolang/values_conversions_test.go new file mode 100644 index 00000000000..e92496c1ac2 --- /dev/null +++ b/gnovm/pkg/gnolang/values_conversions_test.go @@ -0,0 +1,27 @@ +package gnolang + +import ( + "math" + "testing" + + "github.com/cockroachdb/apd" + "github.com/stretchr/testify/require" +) + +func TestConvertUntypedBigdecToFloat(t *testing.T) { + t.Parallel() + + dst := &TypedValue{} + + dec, err := apd.New(-math.MaxInt64, -4).SetFloat64(math.SmallestNonzeroFloat64 / 2) + require.NoError(t, err) + bd := BigdecValue{ + V: dec, + } + + typ := Float64Type + + ConvertUntypedBigdecTo(dst, bd, typ) + + require.Equal(t, float64(0), dst.GetFloat64()) +} diff --git a/gnovm/pkg/gnomod/file_test.go b/gnovm/pkg/gnomod/file_test.go index 7acea3a6096..0012960eb4f 100644 --- a/gnovm/pkg/gnomod/file_test.go +++ b/gnovm/pkg/gnomod/file_test.go @@ -20,11 +20,30 @@ func TestFetchDeps(t *testing.T) { for _, tc := range []struct { desc string modFile File + errorShouldContain string requirements []string stdOutContains []string cachedStdOutContains []string }{ { + desc: "not_exists", + modFile: File{ + Module: &modfile.Module{ + Mod: module.Version{ + Path: "testFetchDeps", + }, + }, + Require: []*modfile.Require{ + { + Mod: module.Version{ + Path: "gno.land/p/demo/does_not_exists", + Version: "v0.0.0", + }, + }, + }, + }, + errorShouldContain: "querychain (gno.land/p/demo/does_not_exists)", + }, { desc: "fetch_gno.land/p/demo/avl", modFile: File{ Module: &modfile.Module{ @@ -89,29 +108,34 @@ func TestFetchDeps(t *testing.T) { defer cleanUpFn() // Fetching dependencies - tc.modFile.FetchDeps(dirPath, testRemote, true) + err := tc.modFile.FetchDeps(dirPath, testRemote, true) + if tc.errorShouldContain != "" { + require.ErrorContains(t, err, tc.errorShouldContain) + } else { + require.Nil(t, err) - // Read dir - entries, err := os.ReadDir(filepath.Join(dirPath, "gno.land", "p", "demo")) - require.Nil(t, err) + // Read dir + entries, err := os.ReadDir(filepath.Join(dirPath, "gno.land", "p", "demo")) + require.Nil(t, err) - // Check dir entries - assert.Equal(t, len(tc.requirements), len(entries)) - for _, e := range entries { - assert.Contains(t, tc.requirements, e.Name()) - } + // Check dir entries + assert.Equal(t, len(tc.requirements), len(entries)) + for _, e := range entries { + assert.Contains(t, tc.requirements, e.Name()) + } - // Check logs - for _, c := range tc.stdOutContains { - assert.Contains(t, buf.String(), c) - } + // Check logs + for _, c := range tc.stdOutContains { + assert.Contains(t, buf.String(), c) + } - buf.Reset() + buf.Reset() - // Try fetching again. Should be cached - tc.modFile.FetchDeps(dirPath, testRemote, true) - for _, c := range tc.cachedStdOutContains { - assert.Contains(t, buf.String(), c) + // Try fetching again. Should be cached + tc.modFile.FetchDeps(dirPath, testRemote, true) + for _, c := range tc.cachedStdOutContains { + assert.Contains(t, buf.String(), c) + } } }) } diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index 7bb51d6558a..3c224bafb87 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -3,7 +3,6 @@ package gnomod import ( "errors" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -172,9 +171,9 @@ func CreateGnoModFile(rootDir, modPath string) error { if modPath == "" { // Check .gno files for package name // and use it as modPath - files, err := ioutil.ReadDir(rootDir) + files, err := os.ReadDir(rootDir) if err != nil { - fmt.Errorf("read dir %q: %w", rootDir, err) + return fmt.Errorf("read dir %q: %w", rootDir, err) } var pkgName gnolang.Name diff --git a/gnovm/pkg/gnomod/parse.go b/gnovm/pkg/gnomod/parse.go index 5bda3c31f70..a6314d5729f 100644 --- a/gnovm/pkg/gnomod/parse.go +++ b/gnovm/pkg/gnomod/parse.go @@ -42,6 +42,34 @@ func ParseAt(dir string) (*File, error) { return gm, nil } +// tries to parse gno mod file given the filename, using Parse and Validate from +// the gnomod package +// +// TODO(tb): replace by `gnomod.ParseAt` ? The key difference is the latter +// looks for gno.mod in parent directories, while this function doesn't. +func ParseGnoMod(fname string) (*File, error) { + file, err := os.Stat(fname) + if err != nil { + return nil, fmt.Errorf("could not read gno.mod file: %w", err) + } + if file.IsDir() { + return nil, fmt.Errorf("invalid gno.mod at %q: is a directory", fname) + } + + b, err := os.ReadFile(fname) + if err != nil { + return nil, fmt.Errorf("could not read gno.mod file: %w", err) + } + gm, err := Parse(fname, b) + if err != nil { + return nil, fmt.Errorf("error parsing gno.mod file at %q: %w", fname, err) + } + if err := gm.Validate(); err != nil { + return nil, fmt.Errorf("error validating gno.mod file at %q: %w", fname, err) + } + return gm, nil +} + // Parse parses and returns a gno.mod file. // // - file is the name of the file, used in positions and errors. diff --git a/gnovm/pkg/gnomod/parse_test.go b/gnovm/pkg/gnomod/parse_test.go index 934531e69c7..61aaa83482b 100644 --- a/gnovm/pkg/gnomod/parse_test.go +++ b/gnovm/pkg/gnomod/parse_test.go @@ -1,9 +1,12 @@ package gnomod import ( + "path/filepath" "testing" + "github.com/gnolang/gno/tm2/pkg/testutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestModuleDeprecated(t *testing.T) { @@ -167,3 +170,57 @@ func TestParseDraft(t *testing.T) { }) } } + +func TestParseGnoMod(t *testing.T) { + pkgDir := "bar" + for _, tc := range []struct { + desc, modData, modPath, errShouldContain string + }{ + { + desc: "file not exists", + modData: `module foo`, + modPath: filepath.Join(pkgDir, "mod.gno"), + errShouldContain: "could not read gno.mod file:", + }, + { + desc: "file path is dir", + modData: `module foo`, + modPath: pkgDir, + errShouldContain: "is a directory", + }, + { + desc: "valid gno.mod file", + modData: `module foo`, + modPath: filepath.Join(pkgDir, "gno.mod"), + }, + { + desc: "error parsing gno.mod", + modData: `module foo v0.0.0`, + modPath: filepath.Join(pkgDir, "gno.mod"), + errShouldContain: "error parsing gno.mod file at", + }, + { + desc: "error validating gno.mod", + modData: `require bar v0.0.0`, + modPath: filepath.Join(pkgDir, "gno.mod"), + errShouldContain: "error validating gno.mod file at", + }, + } { + t.Run(tc.desc, func(t *testing.T) { + // Create test dir + tempDir, cleanUpFn := testutils.NewTestCaseDir(t) + require.NotNil(t, tempDir) + defer cleanUpFn() + + // Create gno package + createGnoModPkg(t, tempDir, pkgDir, tc.modData) + + _, err := ParseGnoMod(filepath.Join(tempDir, tc.modPath)) + if tc.errShouldContain != "" { + assert.ErrorContains(t, err, tc.errShouldContain) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/gnovm/pkg/gnomod/read.go b/gnovm/pkg/gnomod/read.go index 9bbed3c4651..e279d66344d 100644 --- a/gnovm/pkg/gnomod/read.go +++ b/gnovm/pkg/gnomod/read.go @@ -774,7 +774,7 @@ func parseReplace(filename string, line *modfile.Line, verb string, args []strin if strings.Contains(ns, "@") { return nil, errorf("replacement module must match format 'path version', not 'path@version'") } - return nil, errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)") + return nil, errorf("replacement module without version must be directory path (rooted or starting with . or ..)") } if filepath.Separator == '/' && strings.Contains(ns, `\`) { return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)") diff --git a/gnovm/pkg/integration/coverage.go b/gnovm/pkg/integration/coverage.go new file mode 100644 index 00000000000..017f5f9de88 --- /dev/null +++ b/gnovm/pkg/integration/coverage.go @@ -0,0 +1,68 @@ +package integration + +import ( + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/rogpeppe/go-internal/testscript" +) + +var coverageEnv struct { + coverdir string +} + +func init() { + flag.StringVar(&coverageEnv.coverdir, + "txtarcoverdir", "", "write testscripts coverage intermediate files to this directory") +} + +// ResolveCoverageDir attempts to resolve the coverage directory from the 'TXTARCOVERDIR' +// environment variable first, and if not set, from the 'test.txtarcoverdir' flag. +// It returns the resolved directory and a boolean indicating if the resolution was successful. +func ResolveCoverageDir() (string, bool) { + // Attempt to resolve the cover directory from the environment variable or flag + coverdir := os.Getenv("TXTARCOVERDIR") + if coverdir == "" { + coverdir = coverageEnv.coverdir + } + + return coverdir, coverdir != "" +} + +// SetupTestscriptsCoverage sets up the given testscripts environment for coverage. +// It will mostly override `GOCOVERDIR` with the target cover directory +func SetupTestscriptsCoverage(p *testscript.Params, coverdir string) error { + // Check if the given coverage directory exist + info, err := os.Stat(coverdir) + if err != nil { + return fmt.Errorf("output directory %q inaccessible: %w", coverdir, err) + } else if !info.IsDir() { + return fmt.Errorf("output %q not a directory", coverdir) + } + + // We need to have an absolute path here, because current directory + // context will change while executing testscripts. + if !filepath.IsAbs(coverdir) { + var err error + if coverdir, err = filepath.Abs(coverdir); err != nil { + return fmt.Errorf("unable to determine absolute path of %q: %w", coverdir, err) + } + } + + // Backup the original setup function + origSetup := p.Setup + p.Setup = func(env *testscript.Env) error { + if origSetup != nil { + // Call previous setup first + origSetup(env) + } + + // Override `GOCOVEDIR` directory for sub-execution + env.Setenv("GOCOVERDIR", coverdir) + return nil + } + + return nil +} diff --git a/gnovm/pkg/integration/gno.go b/gnovm/pkg/integration/gno.go new file mode 100644 index 00000000000..56e18bbb5db --- /dev/null +++ b/gnovm/pkg/integration/gno.go @@ -0,0 +1,108 @@ +package integration + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + + osm "github.com/gnolang/gno/tm2/pkg/os" + "github.com/rogpeppe/go-internal/testscript" +) + +// SetupGno prepares the given testscript environment for tests that utilize the gno command. +// If the `gno` binary doesn't exist, it's built using the `go build` command into the specified buildDir. +// The function also include the `gno` command into `p.Cmds` to and wrap environment into p.Setup +// to correctly set up the environment variables needed for the `gno` command. +func SetupGno(p *testscript.Params, buildDir string) error { + // Try to fetch `GNOROOT` from the environment variables + gnoroot := os.Getenv("GNOROOT") + if gnoroot == "" { + // If `GNOROOT` isn't set, determine the root directory of github.com/gnolang/gno + goModPath, err := exec.Command("go", "env", "GOMOD").CombinedOutput() + if err != nil { + return fmt.Errorf("unable to determine gno root directory") + } + + gnoroot = filepath.Dir(string(goModPath)) + } + + if !osm.DirExists(buildDir) { + return fmt.Errorf("%q does not exist or is not a directory", buildDir) + } + + // Determine the path to the gno binary within the build directory + gnoBin := filepath.Join(buildDir, "gno") + if _, err := os.Stat(gnoBin); err != nil { + if !errors.Is(err, os.ErrNotExist) { + // Handle other potential errors from os.Stat + return err + } + + // Build a fresh gno binary in a temp directory + gnoArgsBuilder := []string{"build", "-o", gnoBin} + + // Forward `-covermode` settings if set + if coverMode := testing.CoverMode(); coverMode != "" { + gnoArgsBuilder = append(gnoArgsBuilder, "-covermode", coverMode) + } + + // Append the path to the gno command source + gnoArgsBuilder = append(gnoArgsBuilder, filepath.Join(gnoroot, "gnovm", "cmd", "gno")) + + if err = exec.Command("go", gnoArgsBuilder...).Run(); err != nil { + return fmt.Errorf("unable to build gno binary: %w", err) + } + } + + // Store the original setup scripts for potential wrapping + origSetup := p.Setup + p.Setup = func(env *testscript.Env) error { + // If there's an original setup, execute it + if origSetup != nil { + if err := origSetup(env); err != nil { + return err + } + } + + // Set the GNOROOT environment variable + env.Setenv("GNOROOT", gnoroot) + + // Create a temporary home directory because certain commands require access to $HOME/.cache/go-build + home, err := os.MkdirTemp("", "gno") + if err != nil { + return fmt.Errorf("unable to create temporary home directory: %w", err) + } + env.Setenv("HOME", home) + + // Cleanup home folder + env.Defer(func() { os.RemoveAll(home) }) + + return nil + } + + // Initialize cmds map if needed + if p.Cmds == nil { + p.Cmds = make(map[string]func(ts *testscript.TestScript, neg bool, args []string)) + } + + // Register the gno command for testscripts + p.Cmds["gno"] = func(ts *testscript.TestScript, neg bool, args []string) { + err := ts.Exec(gnoBin, args...) + if err != nil { + ts.Logf("gno command error: %v", err) + } + + commandSucceeded := (err == nil) + successExpected := !neg + + // Compare the command's success status with the expected outcome. + if commandSucceeded != successExpected { + ts.Fatalf("unexpected gno command outcome (err=%t expected=%t)", commandSucceeded, successExpected) + } + } + + return nil +} diff --git a/gnovm/pkg/repl/repl.go b/gnovm/pkg/repl/repl.go index 0f60b948f39..c7786cf08b0 100644 --- a/gnovm/pkg/repl/repl.go +++ b/gnovm/pkg/repl/repl.go @@ -161,7 +161,7 @@ func (r *Repl) Process(input string) (out string, err error) { return r.handleExpression(exp) } - return "", fmt.Errorf("error parsing code:\n\t- as expression (error: %q)\n\t- as declarations (error: %q)", expErr.Error(), declErr.Error()) + return "", fmt.Errorf("error parsing code:\n\t- as expression: %w\n\t- as declarations: %w", expErr, declErr) } func (r *Repl) handleExpression(e *ast.File) (string, error) { diff --git a/gnovm/pkg/repl/repl_test.go b/gnovm/pkg/repl/repl_test.go index 3c4d1f7c6c6..09c350dd49a 100644 --- a/gnovm/pkg/repl/repl_test.go +++ b/gnovm/pkg/repl/repl_test.go @@ -199,6 +199,8 @@ func TestRepl(t *testing.T) { } func TestReplOpts(t *testing.T) { + t.Parallel() + require := require.New(t) r := NewRepl(WithStd(nil, nil, nil), WithStore(nil)) @@ -212,6 +214,8 @@ func TestReplOpts(t *testing.T) { } func TestReplReset(t *testing.T) { + t.Parallel() + require := require.New(t) r := NewRepl() diff --git a/gnovm/stdlibs/encoding/encoding.gno b/gnovm/stdlibs/encoding/encoding.gno new file mode 100644 index 00000000000..50acf3c23a1 --- /dev/null +++ b/gnovm/stdlibs/encoding/encoding.gno @@ -0,0 +1,54 @@ +// Copyright 2013 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 encoding defines interfaces shared by other packages that +// convert data to and from byte-level and textual representations. +// Packages that check for these interfaces include encoding/gob, +// encoding/json, and encoding/xml. As a result, implementing an +// interface once can make a type useful in multiple encodings. +// Standard types that implement these interfaces include time.Time and net.IP. +// The interfaces come in pairs that produce and consume encoded data. +// +// Adding encoding/decoding methods to existing types may constitute a breaking change, +// as they can be used for serialization in communicating with programs +// written with different library versions. +// The policy for packages maintained by the Go project is to only allow +// the addition of marshaling functions if no existing, reasonable marshaling exists. +package encoding + +// BinaryMarshaler is the interface implemented by an object that can +// marshal itself into a binary form. +// +// MarshalBinary encodes the receiver into a binary form and returns the result. +type BinaryMarshaler interface { + MarshalBinary() (data []byte, err error) +} + +// BinaryUnmarshaler is the interface implemented by an object that can +// unmarshal a binary representation of itself. +// +// UnmarshalBinary must be able to decode the form generated by MarshalBinary. +// UnmarshalBinary must copy the data if it wishes to retain the data +// after returning. +type BinaryUnmarshaler interface { + UnmarshalBinary(data []byte) error +} + +// TextMarshaler is the interface implemented by an object that can +// marshal itself into a textual form. +// +// MarshalText encodes the receiver into UTF-8-encoded text and returns the result. +type TextMarshaler interface { + MarshalText() (text []byte, err error) +} + +// TextUnmarshaler is the interface implemented by an object that can +// unmarshal a textual representation of itself. +// +// UnmarshalText must be able to decode the form generated by MarshalText. +// UnmarshalText must copy the text if it wishes to retain the text +// after returning. +type TextUnmarshaler interface { + UnmarshalText(text []byte) error +} diff --git a/gnovm/stdlibs/hash/adler32/adler32.gno b/gnovm/stdlibs/hash/adler32/adler32.gno new file mode 100644 index 00000000000..38d644d1ee5 --- /dev/null +++ b/gnovm/stdlibs/hash/adler32/adler32.gno @@ -0,0 +1,135 @@ +// 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 adler32 implements the Adler-32 checksum. +// +// It is defined in RFC 1950: +// +// Adler-32 is composed of two sums accumulated per byte: s1 is +// the sum of all bytes, s2 is the sum of all s1 values. Both sums +// are done modulo 65521. s1 is initialized to 1, s2 to zero. The +// Adler-32 checksum is stored as s2*65536 + s1 in most- +// significant-byte first (network) order. +package adler32 + +import ( + "errors" + "hash" +) + +const ( + // mod is the largest prime that is less than 65536. + mod = 65521 + // nmax is the largest n such that + // 255 * n * (n+1) / 2 + (n+1) * (mod-1) <= 2^32-1. + // It is mentioned in RFC 1950 (search for "5552"). + nmax = 5552 +) + +// The size of an Adler-32 checksum in bytes. +const Size = 4 + +// digest represents the partial evaluation of a checksum. +// The low 16 bits are s1, the high 16 bits are s2. +type digest uint32 + +func (d *digest) Reset() { *d = 1 } + +// New returns a new hash.Hash32 computing the Adler-32 checksum. Its +// Sum method will lay the value out in big-endian byte order. The +// returned Hash32 also implements encoding.BinaryMarshaler and +// encoding.BinaryUnmarshaler to marshal and unmarshal the internal +// state of the hash. +func New() hash.Hash32 { + d := new(digest) + d.Reset() + return d +} + +func (d *digest) Size() int { return Size } + +func (d *digest) BlockSize() int { return 4 } + +const ( + magic = "adl\x01" + marshaledSize = len(magic) + 4 +) + +func (d *digest) MarshalBinary() ([]byte, error) { + b := make([]byte, 0, marshaledSize) + b = append(b, magic...) + b = appendUint32(b, uint32(*d)) + return b, nil +} + +func (d *digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic) || string(b[:len(magic)]) != magic { + return errors.New("hash/adler32: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("hash/adler32: invalid hash state size") + } + *d = digest(readUint32(b[len(magic):])) + return nil +} + +func appendUint32(b []byte, x uint32) []byte { + a := [4]byte{ + byte(x >> 24), + byte(x >> 16), + byte(x >> 8), + byte(x), + } + return append(b, a[:]...) +} + +func readUint32(b []byte) uint32 { + _ = b[3] + return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 +} + +// Add p to the running checksum d. +func update(d digest, p []byte) digest { + s1, s2 := uint32(d&0xffff), uint32(d>>16) + for len(p) > 0 { + var q []byte + if len(p) > nmax { + p, q = p[:nmax], p[nmax:] + } + for len(p) >= 4 { + s1 += uint32(p[0]) + s2 += s1 + s1 += uint32(p[1]) + s2 += s1 + s1 += uint32(p[2]) + s2 += s1 + s1 += uint32(p[3]) + s2 += s1 + p = p[4:] + } + for _, x := range p { + s1 += uint32(x) + s2 += s1 + } + s1 %= mod + s2 %= mod + p = q + } + return digest(s2<<16 | s1) +} + +func (d *digest) Write(p []byte) (nn int, err error) { + *d = update(*d, p) + return len(p), nil +} + +func (d *digest) Sum32() uint32 { return uint32(*d) } + +func (d *digest) Sum(in []byte) []byte { + s := uint32(*d) + return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s)) +} + +// Checksum returns the Adler-32 checksum of data. +func Checksum(data []byte) uint32 { return uint32(update(1, data)) } diff --git a/gnovm/stdlibs/hash/hash.gno b/gnovm/stdlibs/hash/hash.gno new file mode 100644 index 00000000000..62cf6a45184 --- /dev/null +++ b/gnovm/stdlibs/hash/hash.gno @@ -0,0 +1,58 @@ +// 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 hash provides interfaces for hash functions. +package hash + +import "io" + +// Hash is the common interface implemented by all hash functions. +// +// Hash implementations in the standard library (e.g. hash/crc32 and +// crypto/sha256) implement the encoding.BinaryMarshaler and +// encoding.BinaryUnmarshaler interfaces. Marshaling a hash implementation +// allows its internal state to be saved and used for additional processing +// later, without having to re-write the data previously written to the hash. +// The hash state may contain portions of the input in its original form, +// which users are expected to handle for any possible security implications. +// +// Compatibility: Any future changes to hash or crypto packages will endeavor +// to maintain compatibility with state encoded using previous versions. +// That is, any released versions of the packages should be able to +// decode data written with any previously released version, +// subject to issues such as security fixes. +// See the Go compatibility document for background: https://golang.org/doc/go1compat +type Hash interface { + // Write (via the embedded io.Writer interface) adds more data to the running hash. + // It never returns an error. + io.Writer + + // Sum appends the current hash to b and returns the resulting slice. + // It does not change the underlying hash state. + Sum(b []byte) []byte + + // Reset resets the Hash to its initial state. + Reset() + + // Size returns the number of bytes Sum will return. + Size() int + + // BlockSize returns the hash's underlying block size. + // The Write method must be able to accept any amount + // of data, but it may operate more efficiently if all writes + // are a multiple of the block size. + BlockSize() int +} + +// Hash32 is the common interface implemented by all 32-bit hash functions. +type Hash32 interface { + Hash + Sum32() uint32 +} + +// Hash64 is the common interface implemented by all 64-bit hash functions. +type Hash64 interface { + Hash + Sum64() uint64 +} diff --git a/gnovm/stdlibs/hash/marshal_test.gno b/gnovm/stdlibs/hash/marshal_test.gno new file mode 100644 index 00000000000..b31d35faa77 --- /dev/null +++ b/gnovm/stdlibs/hash/marshal_test.gno @@ -0,0 +1,87 @@ +// Copyright 2017 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. + +// Test that the hashes in the standard library implement +// BinaryMarshaler, BinaryUnmarshaler, +// and lock in the current representations. + +package hash + +import ( + "bytes" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "encoding" + "encoding/hex" + "hash" + "hash/adler32" + "testing" +) + +func fromHex(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +var marshalTests = []struct { + name string + new func() hash.Hash + golden []byte +}{ + {"adler32", func() hash.Hash { return adler32.New() }, fromHex("61646c01460a789d")}, +} + +func TestMarshalHash(t *testing.T) { + for _, tt := range marshalTests { + t.Run(tt.name, func(t *testing.T) { + buf := make([]byte, 256) + for i := range buf { + buf[i] = byte(i) + } + + h := tt.new() + h.Write(buf[:256]) + sum := h.Sum(nil) + + h2 := tt.new() + h3 := tt.new() + const split = 249 + for i := 0; i < split; i++ { + h2.Write(buf[i : i+1]) + } + h2m, ok := h2.(encoding.BinaryMarshaler) + if !ok { + t.Fatalf("Hash does not implement MarshalBinary") + } + enc, err := h2m.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary: %v", err) + } + if !bytes.Equal(enc, tt.golden) { + t.Errorf("MarshalBinary = %x, want %x", enc, tt.golden) + } + h3u, ok := h3.(encoding.BinaryUnmarshaler) + if !ok { + t.Fatalf("Hash does not implement UnmarshalBinary") + } + if err := h3u.UnmarshalBinary(enc); err != nil { + t.Fatalf("UnmarshalBinary: %v", err) + } + h2.Write(buf[split:]) + h3.Write(buf[split:]) + sum2 := h2.Sum(nil) + sum3 := h3.Sum(nil) + if !bytes.Equal(sum2, sum) { + t.Fatalf("Sum after MarshalBinary = %x, want %x", sum2, sum) + } + if !bytes.Equal(sum3, sum) { + t.Fatalf("Sum after UnmarshalBinary = %x, want %x", sum3, sum) + } + }) + } +} diff --git a/gnovm/stdlibs/io/export_test.gno b/gnovm/stdlibs/io/export_test.gno index fa3e8e76f61..6204ffc4591 100644 --- a/gnovm/stdlibs/io/export_test.gno +++ b/gnovm/stdlibs/io/export_test.gno @@ -5,4 +5,8 @@ package io // exported for test -var ErrInvalidWrite = errInvalidWrite +var ( + ErrInvalidWrite = errInvalidWrite + ErrWhence = errWhence + ErrOffset = errOffset +) diff --git a/gnovm/stdlibs/io/io.gno b/gnovm/stdlibs/io/io.gno index 54caf32cb95..6ee52cfe293 100644 --- a/gnovm/stdlibs/io/io.gno +++ b/gnovm/stdlibs/io/io.gno @@ -16,6 +16,8 @@ import ( "errors" ) +// TODO: implement rest of io package after sync package added. + // Seek whence values. const ( SeekStart = 0 // seek relative to the origin of the file @@ -477,6 +479,15 @@ func (l *LimitedReader) Read(p []byte) (n int, err error) { // NewSectionReader returns a SectionReader that reads from r // starting at offset off and stops with EOF after n bytes. func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader { + var remaining int64 + const maxInt64 = 1<<63 - 1 + if off <= maxInt64-n { + remaining = n + off + } else { + // Overflow, with no way to return error. + // Assume we can read up to an offset of `1<<63 - 1` bytes in this case. + remaining = maxInt64 + } return &SectionReader{r, off, off, off + n} } @@ -543,6 +554,53 @@ func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) { // Size returns the size of the section in bytes. func (s *SectionReader) Size() int64 { return s.limit - s.base } +// An OffsetWriter maps writers at offset base to offset base + off in the underlying writer. +type OffsetWriter struct { + w WriterAt + base int64 // the original offset + off int64 // the current offset +} + +// NewOffsetWriter returns a new OffsetWriter that writes to w starting at offset off. +func NewOffsetWriter(w WriterAt, off int64) *OffsetWriter { + return &OffsetWriter{w: w, off: off} +} + +func (o *OffsetWriter) Write(p []byte) (n int, err error) { + // n, err = o.w.WriterAt(p, o.off) + wa := o.w + n, err = wa.WriteAt(p, o.off) + o.off += int64(n) + return +} + +func (o *OffsetWriter) WriteAt(p []byte, off int64) (n int, err error) { + if off < 0 { + return 0, errOffset + } + + off += o.base + return o.w.WriteAt(p, off) +} + +func (o *OffsetWriter) Seek(offset int64, whence int) (int64, error) { + switch whence { + default: + return 0, errWhence + case SeekStart: + offset += o.base + case SeekCurrent: + offset += o.off + } + + if offset < o.base { + return 0, errOffset + } + + o.off = offset + return offset - o.base, nil +} + // TeeReader returns a Reader that writes to w what it reads from r. // All reads from r performed through it are matched with // corresponding writes to w. There is no internal buffering - @@ -614,7 +672,12 @@ func (discard) ReadFrom(r Reader) (n int64, err error) { // NopCloser returns a ReadCloser with a no-op Close method wrapping // the provided Reader r. +// If r implements WriterTo, the returned ReadCloser will implement WriterTo +// by forwarding calls to r. func NopCloser(r Reader) ReadCloser { + if _, ok := r.(WriterTo); ok { + return nopCloserWriterTo{r} + } return nopCloser{r} } @@ -624,6 +687,16 @@ type nopCloser struct { func (nopCloser) Close() error { return nil } +type nopCloserWriterTo struct { + Reader +} + +func (nopCloserWriterTo) Close() error { return nil } + +func (c nopCloserWriterTo) WriteTo(w Writer) (n int64, err error) { + return c.Reader.(WriterTo).WriteTo(w) +} + // ReadAll reads from r until an error or EOF and returns the data it read. // A successful call returns err == nil, not err == EOF. Because ReadAll is // defined to read from src until EOF, it does not treat an EOF from Read @@ -643,5 +716,9 @@ func ReadAll(r Reader) ([]byte, error) { } return b, err } + if len(b) == cap(b) { + // Add more capacity (let append pick how much). + b = append(b, 0)[:len(b)] + } } } diff --git a/gnovm/stdlibs/io/io_test.gno b/gnovm/stdlibs/io/io_test.gno index a97f6b8c075..613b7d13e35 100644 --- a/gnovm/stdlibs/io/io_test.gno +++ b/gnovm/stdlibs/io/io_test.gno @@ -1,14 +1,15 @@ +package io_test + // 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 io_test - import ( "bytes" "errors" "fmt" "io" + "os" "strings" "testing" ) @@ -459,3 +460,84 @@ func TestCopyLargeWriter(t *testing.T) { t.Errorf("Copy error: got %v, want %v", err, want) } } + +func TestNopCloserWriterToForwarding(t *testing.T) { + for _, tc := range [...]struct { + Name string + r io.Reader + }{ + {"not a WriterTo", io.Reader(nil)}, + {"a WriterTo", struct { + io.Reader + io.WriterTo + }{}}, + } { + nc := io.NopCloser(tc.r) + + _, expected := tc.r.(io.WriterTo) + _, got := nc.(io.WriterTo) + if expected != got { + t.Errorf("NopCloser incorrectly forwards WriterTo for %s, got %t want %t", tc.Name, got, expected) + } + } +} + +// XXX os.CreateTemp is not available for now +// func TestOffsetWriter_Seek(t *testing.T) { +// tmpfilename := "TestOffsetWriter_Seek" +// tmpfile, err := os.CreateTemp(t.TempDir(), tmpfilename) +// if err != nil || tmpfile == nil { +// t.Fatalf("CreateTemp(%s) failed: %v", tmpfilename, err) +// } +// defer tmpfile.Close() +// w := NewOffsetWriter(tmpfile, 0) + +// // Should throw error errWhence if whence is not valid +// t.Run("errWhence", func(t *testing.T) { +// for _, whence := range []int{-3, -2, -1, 3, 4, 5} { +// var offset int64 = 0 +// gotOff, gotErr := w.Seek(offset, whence) +// if gotOff != 0 || gotErr != ErrWhence { +// t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", +// whence, offset, gotOff, gotErr, 0, ErrWhence) +// } +// } +// }) + +// // Should throw error errOffset if offset is negative +// t.Run("errOffset", func(t *testing.T) { +// for _, whence := range []int{SeekStart, SeekCurrent} { +// for offset := int64(-3); offset < 0; offset++ { +// gotOff, gotErr := w.Seek(offset, whence) +// if gotOff != 0 || gotErr != ErrOffset { +// t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", +// whence, offset, gotOff, gotErr, 0, ErrOffset) +// } +// } +// } +// }) + +// // Normal tests +// t.Run("normal", func(t *testing.T) { +// tests := []struct { +// offset int64 +// whence int +// returnOff int64 +// }{ +// // keep in order +// {whence: SeekStart, offset: 1, returnOff: 1}, +// {whence: SeekStart, offset: 2, returnOff: 2}, +// {whence: SeekStart, offset: 3, returnOff: 3}, +// {whence: SeekCurrent, offset: 1, returnOff: 4}, +// {whence: SeekCurrent, offset: 2, returnOff: 6}, +// {whence: SeekCurrent, offset: 3, returnOff: 9}, +// } +// for idx, tt := range tests { +// gotOff, gotErr := w.Seek(tt.offset, tt.whence) +// if gotOff != tt.returnOff || gotErr != nil { +// t.Errorf("%d:: For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, )", +// idx+1, tt.whence, tt.offset, gotOff, gotErr, tt.returnOff) +// } +// } +// }) +// } diff --git a/gnovm/stdlibs/io/ioutil/ioutil.gno b/gnovm/stdlibs/io/ioutil/ioutil.gno deleted file mode 100644 index 935031c0511..00000000000 --- a/gnovm/stdlibs/io/ioutil/ioutil.gno +++ /dev/null @@ -1,83 +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 ioutil implements some I/O utility functions. -// -// As of Go 1.16, the same functionality is now provided -// by package io or package os, and those implementations -// should be preferred in new code. -// See the specific function documentation for details. -package ioutil - -import ( - "io" -) - -// ReadAll reads from r until an error or EOF and returns the data it read. -// A successful call returns err == nil, not err == EOF. Because ReadAll is -// defined to read from src until EOF, it does not treat an EOF from Read -// as an error to be reported. -// -// As of Go 1.16, this function simply calls io.ReadAll. -func ReadAll(r io.Reader) ([]byte, error) { - return io.ReadAll(r) -} - -/* XXX os and os/fs removed. -// ReadFile reads the file named by filename and returns the contents. -// A successful call returns err == nil, not err == EOF. Because ReadFile -// reads the whole file, it does not treat an EOF from Read as an error -// to be reported. -// -// As of Go 1.16, this function simply calls os.ReadFile. -func ReadFile(filename string) ([]byte, error) { - return os.ReadFile(filename) -} - -// WriteFile writes data to a file named by filename. -// If the file does not exist, WriteFile creates it with permissions perm -// (before umask); otherwise WriteFile truncates it before writing, without changing permissions. -// -// As of Go 1.16, this function simply calls os.WriteFile. -func WriteFile(filename string, data []byte, perm fs.FileMode) error { - return os.WriteFile(filename, data, perm) -} - -// ReadDir reads the directory named by dirname and returns -// a list of fs.FileInfo for the directory's contents, -// sorted by filename. If an error occurs reading the directory, -// ReadDir returns no directory entries along with the error. -// -// As of Go 1.16, os.ReadDir is a more efficient and correct choice: -// it returns a list of fs.DirEntry instead of fs.FileInfo, -// and it returns partial results in the case of an error -// midway through reading a directory. -func ReadDir(dirname string) ([]fs.FileInfo, error) { - f, err := os.Open(dirname) - if err != nil { - return nil, err - } - list, err := f.Readdir(-1) - f.Close() - if err != nil { - return nil, err - } - sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() }) - return list, nil -} -*/ - -// NopCloser returns a ReadCloser with a no-op Close method wrapping -// the provided Reader r. -// -// As of Go 1.16, this function simply calls io.NopCloser. -func NopCloser(r io.Reader) io.ReadCloser { - return io.NopCloser(r) -} - -// Discard is an io.Writer on which all Write calls succeed -// without doing anything. -// -// As of Go 1.16, this value is simply io.Discard. -var Discard io.Writer = io.Discard diff --git a/gnovm/stdlibs/io/multi_test.gno b/gnovm/stdlibs/io/multi_test.gno index ee800b3ec24..31345279318 100644 --- a/gnovm/stdlibs/io/multi_test.gno +++ b/gnovm/stdlibs/io/multi_test.gno @@ -1,9 +1,9 @@ +package io_test + // Copyright 2010 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 io_test - import ( "bytes" "crypto/sha1" diff --git a/gnovm/stdlibs/std/crypto.gno b/gnovm/stdlibs/std/crypto.gno index 3ebd802dc3f..8d005dccf5c 100644 --- a/gnovm/stdlibs/std/crypto.gno +++ b/gnovm/stdlibs/std/crypto.gno @@ -6,6 +6,11 @@ func (a Address) String() string { return string(a) } +// IsValid checks if the address is of specific length. Doesn't check prefix or checksum for the address +func (a Address) IsValid() bool { + return len(a) == RawAddressSize*2 // hex length +} + const RawAddressSize = 20 type RawAddress [RawAddressSize]byte diff --git a/gnovm/stdlibs/std/crypto_test.gno b/gnovm/stdlibs/std/crypto_test.gno new file mode 100644 index 00000000000..293f3e06945 --- /dev/null +++ b/gnovm/stdlibs/std/crypto_test.gno @@ -0,0 +1,30 @@ +package std + +import ( + "testing" +) + +func TestValid(t *testing.T) { + type test struct { + inputAddress Address + expected bool + } + + testCases := []test{ + {inputAddress: "g1f4v282mwyhu29afke4vq5r2xzcm6z3ftnugcnv", expected: true}, + {inputAddress: "g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa", expected: true}, + {inputAddress: "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", expected: true}, + {inputAddress: "g14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa", expected: true}, + {inputAddress: "", expected: false}, + {inputAddress: "000000000000", expected: false}, + {inputAddress: "0000000000000000000000000000000000000000000000000000000000000000000000", expected: false}, + } + + for _, tc := range testCases { + result := tc.inputAddress.IsValid() + + if result != tc.expected { + t.Fatalf("Expected: %t, got: %t", tc.expected, result) + } + } +} diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index fb230a0cf86..8931266eb9a 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -581,13 +581,3 @@ func typedByteArray(ln int, bz *gno.ArrayValue) gno.TypedValue { tv := gno.TypedValue{T: &gno.ArrayType{Len: ln, Elt: gno.Uint8Type}, V: bz} return tv } - -func typedByteSlice(bz *gno.SliceValue) gno.TypedValue { - tv := gno.TypedValue{T: &gno.SliceType{Elt: gno.Uint8Type}, V: bz} - return tv -} - -func typedNil(t gno.Type) gno.TypedValue { - tv := gno.TypedValue{T: t, V: nil} - return tv -} diff --git a/gnovm/stdlibs/stdshim/crypto.gno b/gnovm/stdlibs/stdshim/crypto.gno index 3ebd802dc3f..8d005dccf5c 100644 --- a/gnovm/stdlibs/stdshim/crypto.gno +++ b/gnovm/stdlibs/stdshim/crypto.gno @@ -6,6 +6,11 @@ func (a Address) String() string { return string(a) } +// IsValid checks if the address is of specific length. Doesn't check prefix or checksum for the address +func (a Address) IsValid() bool { + return len(a) == RawAddressSize*2 // hex length +} + const RawAddressSize = 20 type RawAddress [RawAddressSize]byte diff --git a/gnovm/tests/backup/cli1.gno b/gnovm/tests/backup/cli1.gno index 8eb9e8c40d7..843eb461ac2 100644 --- a/gnovm/tests/backup/cli1.gno +++ b/gnovm/tests/backup/cli1.gno @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "log" "net" "net/http" @@ -13,7 +13,7 @@ func client(uri string) { if err != nil { log.Fatal(err) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } diff --git a/gnovm/tests/backup/cli2.gno b/gnovm/tests/backup/cli2.gno index a9e8e53be3a..0be01d2f1a9 100644 --- a/gnovm/tests/backup/cli2.gno +++ b/gnovm/tests/backup/cli2.gno @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "log" "net" "net/http" @@ -21,7 +21,7 @@ func client(uri string) { if err != nil { log.Fatal(err) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } diff --git a/gnovm/tests/backup/cli3.gno b/gnovm/tests/backup/cli3.gno index 1c696d4bc4c..50b11b9e4c3 100644 --- a/gnovm/tests/backup/cli3.gno +++ b/gnovm/tests/backup/cli3.gno @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "log" "net/http" "net/http/httptest" @@ -13,7 +13,7 @@ func client(uri string) { if err != nil { log.Fatal(err) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } diff --git a/gnovm/tests/backup/cli4.gno b/gnovm/tests/backup/cli4.gno index 147b63c3e6e..aab6405917c 100644 --- a/gnovm/tests/backup/cli4.gno +++ b/gnovm/tests/backup/cli4.gno @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "log" "net/http" "net/http/httptest" @@ -40,7 +40,7 @@ func client(uri string) { if err != nil { log.Fatal(err) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } diff --git a/gnovm/tests/backup/cli5.gno b/gnovm/tests/backup/cli5.gno index a2e1787c996..6b536841a6d 100644 --- a/gnovm/tests/backup/cli5.gno +++ b/gnovm/tests/backup/cli5.gno @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "log" "net/http" "net/http/httptest" @@ -40,7 +40,7 @@ func client(uri string) { if err != nil { log.Fatal(err) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } diff --git a/gnovm/tests/backup/cli6.gno b/gnovm/tests/backup/cli6.gno index 89ae9f8b98d..e97da82736e 100644 --- a/gnovm/tests/backup/cli6.gno +++ b/gnovm/tests/backup/cli6.gno @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "log" "net/http" "net/http/httptest" @@ -41,7 +41,7 @@ func client(uri string) { if err != nil { log.Fatal(err) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } diff --git a/gnovm/tests/backup/file_access.gno b/gnovm/tests/backup/file_access.gno index 6d750c44a36..e81cc6a0bee 100644 --- a/gnovm/tests/backup/file_access.gno +++ b/gnovm/tests/backup/file_access.gno @@ -2,12 +2,11 @@ package main import ( "fmt" - "io/ioutil" - "os" + "io" ) func main() { - file, err := ioutil.TempFile("", "yeagibench") + file, err := io.TempFile("", "yeagibench") if err != nil { panic(err) } diff --git a/gnovm/tests/backup/ioutil.gno b/gnovm/tests/backup/ioutil.gno deleted file mode 100644 index eae22b6ea5a..00000000000 --- a/gnovm/tests/backup/ioutil.gno +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "fmt" - "os" -) - -func main() { - _, err := os.ReadFile("__NotExisting__") - if err != nil { - fmt.Println(err.Error()) - } -} - -// Output: -// open __NotExisting__: no such file or directory diff --git a/gnovm/tests/backup/issue-558.gno b/gnovm/tests/backup/issue-558.gno index d36bb4e18a7..e99566f7634 100644 --- a/gnovm/tests/backup/issue-558.gno +++ b/gnovm/tests/backup/issue-558.gno @@ -4,7 +4,7 @@ import ( "errors" "fmt" "io" - "io/ioutil" + "io" "log" "strings" ) @@ -37,7 +37,7 @@ type pipe struct { func newReadAutoCloser(r io.Reader) readAutoCloser { if _, ok := r.(io.Closer); !ok { - return readAutoCloser{ioutil.NopCloser(r)} + return readAutoCloser{io.NopCloser(r)} } return readAutoCloser{r.(io.ReadCloser)} } @@ -45,7 +45,7 @@ func newReadAutoCloser(r io.Reader) readAutoCloser { func main() { p := &pipe{} p.Reader = newReadAutoCloser(strings.NewReader("test")) - b, err := ioutil.ReadAll(p.Reader) + b, err := io.ReadAll(p.Reader) if err != nil { log.Fatal(err) } diff --git a/gnovm/tests/files/float6.gno b/gnovm/tests/files/float6.gno new file mode 100644 index 00000000000..680b9461975 --- /dev/null +++ b/gnovm/tests/files/float6.gno @@ -0,0 +1,13 @@ +package main + +const ( + SmallestNonzeroFloat64 = 0x1p-1022 * 0x1p-52 // 4.9406564584124654417656879286822137236505980e-324 + DividedByTwo = SmallestNonzeroFloat64 / 2 +) + +func main() { + println(DividedByTwo) +} + +// Output: +// 0 diff --git a/gnovm/tests/files/float7.gno b/gnovm/tests/files/float7.gno new file mode 100644 index 00000000000..f519a963523 --- /dev/null +++ b/gnovm/tests/files/float7.gno @@ -0,0 +1,15 @@ +package main + +const ( + SmallestNonzeroFloat32 = 0x1p-126 * 0x1p-23 // 1.401298464324817070923729583289916131280e-45 + DividedByTwo = SmallestNonzeroFloat32 / 2 +) + +func main() { + var i float32 + i = DividedByTwo + println(i) +} + +// Output: +// 0 diff --git a/gnovm/tests/files/ioutil0.gno b/gnovm/tests/files/io2.gno similarity index 88% rename from gnovm/tests/files/ioutil0.gno rename to gnovm/tests/files/io2.gno index 800d237be22..24655f5040c 100644 --- a/gnovm/tests/files/ioutil0.gno +++ b/gnovm/tests/files/io2.gno @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "log" "strings" ) @@ -10,7 +10,7 @@ import ( func main() { r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.") - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) if err != nil { log.Fatal(err) } diff --git a/gnovm/tests/files/issue-1096.gno b/gnovm/tests/files/issue-1096.gno new file mode 100644 index 00000000000..b0593913401 --- /dev/null +++ b/gnovm/tests/files/issue-1096.gno @@ -0,0 +1,87 @@ +package main + +import "fmt" + +type X struct { + Array [8]int + Test bool +} + +type Y [8]int + +func main() { + x := X{} + x.Array[1] = 888 + println(x.Array[1]) + println(x.Array[2]) + println(x.Test) + + x.manip() + println(x.Array[1]) + println(x.Array[2]) + println(x.Test) + + println("-----") + + y := Y{} + y[1] = 888 + println(y[1]) + println(y[2]) + + y.manip() + println(y[1]) + println(y[2]) + println("-----") + + x = X{} + println(x.Array[1]) + println(x.Array[2]) + println(x.Test) + + x.Array[1] = 888 + println(x.Array[1]) + println(x.Array[2]) + println(x.Test) + + manip(x) + println(x.Array[1]) + println(x.Array[2]) + println(x.Test) +} + +func (x X) manip() { + x.Array[2] = 999 + x.Test = true +} + +func manip(x X) { + x.Array[2] = 999 + x.Test = true +} + +func (y Y) manip() { + y[2] = 111 +} + +// Output: +// 888 +// 0 +// false +// 888 +// 0 +// false +// ----- +// 888 +// 0 +// 888 +// 0 +// ----- +// 0 +// 0 +// false +// 888 +// 0 +// false +// 888 +// 0 +// false diff --git a/gnovm/tests/files/issue-558b.gno b/gnovm/tests/files/issue-558b.gno index 686c73b5c88..55eba88c985 100644 --- a/gnovm/tests/files/issue-558b.gno +++ b/gnovm/tests/files/issue-558b.gno @@ -3,7 +3,7 @@ package main import ( "fmt" "io" - "io/ioutil" + "io" "log" "strings" ) @@ -36,7 +36,7 @@ type pipe struct { func newReadAutoCloser(r io.Reader) readAutoCloser { if _, ok := r.(io.Closer); !ok { - return readAutoCloser{ioutil.NopCloser(r)} + return readAutoCloser{io.NopCloser(r)} } return readAutoCloser{r.(io.ReadCloser)} } diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index fc2820ce00e..0741d0b466a 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -20,7 +20,6 @@ import ( "image" "image/color" "io" - "io/ioutil" "log" "math" "math/big" @@ -356,16 +355,13 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri case "io": pkg := gno.NewPackageNode("io", pkgPath, nil) pkg.DefineGoNativeValue("EOF", io.EOF) + pkg.DefineGoNativeValue("NopCloser", io.NopCloser) pkg.DefineGoNativeValue("ReadFull", io.ReadFull) + pkg.DefineGoNativeValue("ReadAll", io.ReadAll) pkg.DefineGoNativeType(reflect.TypeOf((*io.ReadCloser)(nil)).Elem()) pkg.DefineGoNativeType(reflect.TypeOf((*io.Closer)(nil)).Elem()) pkg.DefineGoNativeType(reflect.TypeOf((*io.Reader)(nil)).Elem()) return pkg, pkg.NewPackage() - case "io/ioutil": - pkg := gno.NewPackageNode("ioutil", pkgPath, nil) - pkg.DefineGoNativeValue("NopCloser", ioutil.NopCloser) - pkg.DefineGoNativeValue("ReadAll", ioutil.ReadAll) - return pkg, pkg.NewPackage() case "log": pkg := gno.NewPackageNode("log", pkgPath, nil) pkg.DefineGoNativeValue("Fatal", log.Fatal) diff --git a/gnovm/tests/integ/invalid-gno-file/gno.mod b/gnovm/tests/integ/invalid-gno-file/gno.mod new file mode 100644 index 00000000000..060e28b9dc4 --- /dev/null +++ b/gnovm/tests/integ/invalid-gno-file/gno.mod @@ -0,0 +1 @@ +module test diff --git a/gnovm/tests/integ/invalid-gno-file/invalid.gno b/gnovm/tests/integ/invalid-gno-file/invalid.gno new file mode 100644 index 00000000000..1e4ff406ada --- /dev/null +++ b/gnovm/tests/integ/invalid-gno-file/invalid.gno @@ -0,0 +1 @@ +packag invalid diff --git a/gnovm/tests/integ/valid2/gno.mod b/gnovm/tests/integ/valid2/gno.mod new file mode 100644 index 00000000000..98a5a0dacc1 --- /dev/null +++ b/gnovm/tests/integ/valid2/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/integ/valid + +require gno.land/p/demo/avl v0.0.0-latest diff --git a/gnovm/tests/integ/valid2/valid.gno b/gnovm/tests/integ/valid2/valid.gno new file mode 100644 index 00000000000..4de283f5d87 --- /dev/null +++ b/gnovm/tests/integ/valid2/valid.gno @@ -0,0 +1,11 @@ +package valid + +import ( + "gno.land/p/demo/avl" +) + +const Foo = "foo" + +func DoNothing(t *avl.Tree) { + // noop +} diff --git a/gnovm/tests/integ/valid2/valid_test.gno b/gnovm/tests/integ/valid2/valid_test.gno new file mode 100644 index 00000000000..2394da5c5ae --- /dev/null +++ b/gnovm/tests/integ/valid2/valid_test.gno @@ -0,0 +1,11 @@ +package valid + +import ( + "testing" + + "gno.land/p/integ/valid" +) + +func TestAlwaysValid(t *testing.T) { + _ = valid.Foo +} diff --git a/gnovm/tests/integ/valid2/z_0_filetest.gno b/gnovm/tests/integ/valid2/z_0_filetest.gno new file mode 100644 index 00000000000..06ab7d0f9a3 --- /dev/null +++ b/gnovm/tests/integ/valid2/z_0_filetest.gno @@ -0,0 +1 @@ +package main diff --git a/gnovm/tests/selector_test.go b/gnovm/tests/selector_test.go index 4d6e9c587b0..1f0b400555b 100644 --- a/gnovm/tests/selector_test.go +++ b/gnovm/tests/selector_test.go @@ -52,6 +52,8 @@ func _printValue(x interface{}) { } func TestSelectors(t *testing.T) { + t.Parallel() + x0 := struct{ F0 int }{1} _printValue(x0.F0) // *ST.F0 // F:0 diff --git a/go.mod b/go.mod index 7c5934e676e..adcb083ea66 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,8 @@ require ( github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 github.com/golang/protobuf v1.5.3 github.com/google/gofuzz v1.2.0 - github.com/gorilla/mux v1.8.0 - github.com/gorilla/websocket v1.5.0 + github.com/gorilla/mux v1.8.1 + github.com/gorilla/websocket v1.5.1 github.com/gotuna/gotuna v0.6.0 github.com/jaekwon/testify v1.6.1 github.com/jmhodges/levigo v1.0.0 diff --git a/go.sum b/go.sum index 01c5574544d..a1ae600ce60 100644 --- a/go.sum +++ b/go.sum @@ -93,14 +93,15 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/csrf v1.7.0/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gotuna/gotuna v0.6.0 h1:N1lQKXEi/lwRp8u3sccTYLhzOffA4QasExz/1M5Riws= github.com/gotuna/gotuna v0.6.0/go.mod h1:F/ecRt29ChB6Ycy1AFIBpBiMNK0j7Heq+gFbLWquhjc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= diff --git a/misc/deployments/staging.gno.land/docker-compose.yml b/misc/deployments/staging.gno.land/docker-compose.yml index 268002902ab..af5e747e653 100644 --- a/misc/deployments/staging.gno.land/docker-compose.yml +++ b/misc/deployments/staging.gno.land/docker-compose.yml @@ -36,8 +36,8 @@ services: - --faucet-url=https://faucet-staging.gno.land/ - --help-chainid=staging - --help-remote=staging.gno.land:36657 - - --pages-dir=/overlay/pages - --views-dir=./gno.land/cmd/gnoweb/views + - --with-analytics volumes: - "./overlay:/overlay:ro" links: diff --git a/misc/devdeps/Makefile b/misc/devdeps/Makefile index 99aea512ed0..54df62cc031 100644 --- a/misc/devdeps/Makefile +++ b/misc/devdeps/Makefile @@ -1,4 +1,3 @@ install: go install mvdan.cc/gofumpt go install google.golang.org/protobuf/cmd/protoc-gen-go - go install golang.org/x/tools/cmd/godoc diff --git a/misc/docker-integration/integration_test.go b/misc/docker-integration/integration_test.go index 6ae0ea1d36e..1142709cc16 100644 --- a/misc/docker-integration/integration_test.go +++ b/misc/docker-integration/integration_test.go @@ -29,6 +29,8 @@ const ( ) func TestDockerIntegration(t *testing.T) { + t.Parallel() + tmpdir, err := os.MkdirTemp(os.TempDir(), "*-gnoland-integration") require.NoError(t, err) diff --git a/misc/docusaurus/.gitignore b/misc/docusaurus/.gitignore new file mode 100644 index 00000000000..b2d6de30624 --- /dev/null +++ b/misc/docusaurus/.gitignore @@ -0,0 +1,20 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/misc/docusaurus/babel.config.js b/misc/docusaurus/babel.config.js new file mode 100644 index 00000000000..e00595dae7d --- /dev/null +++ b/misc/docusaurus/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/misc/docusaurus/docusaurus.config.js b/misc/docusaurus/docusaurus.config.js new file mode 100644 index 00000000000..0ff9e920acd --- /dev/null +++ b/misc/docusaurus/docusaurus.config.js @@ -0,0 +1,158 @@ +// @ts-check +// Note: type annotations allow type checking and IDEs autocompletion + +const lightCodeTheme = require("prism-react-renderer/themes/github"); +const darkCodeTheme = require("prism-react-renderer/themes/dracula"); + +/** @type {import('@docusaurus/types').Config} */ +const config = { + title: "Gno.land Documentation", + favicon: "img/favicon.ico", + url: "https://docs.gno.land", + baseUrl: "/", + + organizationName: "gnolang", + projectName: "gno", + + onBrokenLinks: "throw", + onBrokenMarkdownLinks: "warn", + + i18n: { + defaultLocale: "en", + locales: ["en"], + }, + + presets: [ + [ + "classic", + /** @type {import('@docusaurus/preset-classic').Options} */ + ({ + docs: { + path: "../../docs", + routeBasePath: "/", + sidebarPath: require.resolve("./sidebars.js"), + }, + blog: false, + theme: { + customCss: require.resolve("./src/css/custom.css"), + }, + }), + ], + ], + + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + navbar: { + hideOnScroll: true, + title: "Gno.land", + logo: { + alt: "Gno.land Logo", + src: "img/logo.svg", + srcDark: "img/logo_light.svg", + }, + items: [ + { + type: "docSidebar", + sidebarId: "tutorialSidebar", + position: "left", + label: "Docs", + }, + { + href: "https://github.com/gnolang/gno", + html: ` + + + `, + position: "right", + }, + ], + }, + footer: { + style: "dark", + + links: [ + { + items: [ + { + html: ` + + + + + + + `, + }, + ], + }, + { + title: "Gno Libraries", + items: [ + { + label: "gno-js-client", + href: "https://github.com/gnolang/gno-js-client", + }, + { + label: "tm2-js-client", + href: "https://github.com/gnolang/tm2-js-client", + }, + ], + }, + { + title: "Ecosystem", + items: [ + { + label: "Gno by Example", + href: "https://gno-by-example.com/", + }, + { + label: "Awesome GNO", + href: "https://github.com/gnolang/awesome-gno", + }, + ], + }, + ], + }, + prism: { + theme: lightCodeTheme, + darkTheme: darkCodeTheme, + }, + }), +}; + +module.exports = config; diff --git a/misc/docusaurus/package.json b/misc/docusaurus/package.json new file mode 100644 index 00000000000..7c67713d14c --- /dev/null +++ b/misc/docusaurus/package.json @@ -0,0 +1,47 @@ +{ + "name": "gno-docs", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids", + "typecheck": "tsc", + "embed": "for f in $(find ../../docs -name '*.md'); do embedmd -w $f; done\n" + }, + "dependencies": { + "@docusaurus/core": "2.4.1", + "@docusaurus/preset-classic": "2.4.1", + "@mdx-js/react": "^1.6.22", + "clsx": "^1.2.1", + "prism-react-renderer": "^1.3.5", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "2.4.1", + "@tsconfig/docusaurus": "^1.0.5", + "typescript": "^4.7.4" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "engines": { + "node": ">=16.14" + } +} diff --git a/misc/docusaurus/sidebars.js b/misc/docusaurus/sidebars.js new file mode 100644 index 00000000000..8405d9a05c5 --- /dev/null +++ b/misc/docusaurus/sidebars.js @@ -0,0 +1,113 @@ +// @ts-check + +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const sidebars = { + tutorialSidebar: [ + 'overview', + 'peace', + { + type: 'category', + label: 'Getting Started', + items: [ + 'getting-started/local-setup', + 'getting-started/working-with-key-pairs', + { + type: 'category', + label: 'Setting up Funds', + items: [ + 'getting-started/setting-up-funds/premining-balances', + 'getting-started/setting-up-funds/running-a-faucet', + ] + }, + 'getting-started/setting-up-a-local-chain', + 'getting-started/browsing-gno-source-code', + ], + }, + { + type: 'category', + label: 'How-to Guides', + items: [ + 'how-to-guides/simple-contract', + 'how-to-guides/simple-library', + 'how-to-guides/testing-gno', + 'how-to-guides/deploy', + 'how-to-guides/write-simple-dapp', + 'how-to-guides/creating-grc20', + 'how-to-guides/creating-grc721', + 'how-to-guides/connect-wallet-dapp', + ], + }, + { + type: 'category', + label: 'Reference', + items: [ + 'reference/rpc-endpoints', + 'reference/standard-library', + 'reference/go-gno-compatibility', + { + type: 'category', + label: 'tm2-js-client', + items: [ + 'reference/tm2-js-client/tm2-js-getting-started', + 'reference/tm2-js-client/tm2-js-wallet', + { + type: 'category', + label: 'Provider', + items: [ + 'reference/tm2-js-client/Provider/tm2-js-provider', + 'reference/tm2-js-client/Provider/tm2-js-json-rpc-provider', + 'reference/tm2-js-client/Provider/tm2-js-ws-provider', + 'reference/tm2-js-client/Provider/tm2-js-utility', + ] + }, + { + type: 'category', + label: 'Signer', + items: [ + 'reference/tm2-js-client/Signer/tm2-js-signer', + 'reference/tm2-js-client/Signer/tm2-js-key', + 'reference/tm2-js-client/Signer/tm2-js-ledger', + ] + }, + ] + }, + { + type: 'category', + label: 'gno-js-client', + items: [ + 'reference/gno-js-client/gno-js-getting-started', + 'reference/gno-js-client/gno-js-provider', + 'reference/gno-js-client/gno-js-wallet', + ] + }, + ], + }, + { + type: 'category', + label: 'Explanation', + items: [ + 'explanation/realms', + 'explanation/tendermint2', + 'explanation/gnovm', + 'explanation/proof-of-contribution', + 'explanation/gno-language', + 'explanation/gno-modules', + 'explanation/gno-test', + 'explanation/from-go-to-gno', + { + type: 'category', + label: 'Gno Tooling', + items: [ + 'explanation/gno-tooling/cli/gno-tooling-gno', + 'explanation/gno-tooling/cli/gno-tooling-gnokey', + 'explanation/gno-tooling/cli/gno-tooling-gnofaucet', + 'explanation/gno-tooling/cli/gno-tooling-gnoland', + 'explanation/gno-tooling/cli/gno-tooling-tm2txsync', + ] + }, + ], + }, + ], +}; + +module.exports = sidebars; diff --git a/misc/docusaurus/src/css/custom.css b/misc/docusaurus/src/css/custom.css new file mode 100644 index 00000000000..58afeea4a35 --- /dev/null +++ b/misc/docusaurus/src/css/custom.css @@ -0,0 +1,280 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;300;400;600&display=swap"); +/** + * Any CSS included here will be global. The classic template + * bundles Infima by default. Infima is a CSS framework designed to + * work well for content-centric websites. + */ + +/* You can override the default Infima variables here. */ +:root { + --gno-text-font: "Inter"; + --ifm-font-family-base: var(--gno-text-font), -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; + + --ifm-color-primary: #000000; + --ifm-color-primary-light: #6f6f6f; + --ifm-color-primary-lighter: #efefef; + --ifm-color-primary-lightest: #ffffff; + --ifm-font-color-base: #6f6f6f; + --ifm-code-font-size: 95%; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); + --ifm-global-radius: 1rem; + + --ring-offset-shadow: 0 0 #0000; + --ring-shadow: 0 0 #0000; + --shadow: 14px 26px 100px -6px rgba(0, 0, 0, 0.09); + + --ifm-color-content: var(--ifm-color-primary-light); + --ifm-font-color-base: var(--ifm-color-primary-light); + --ifm-heading-color: var(--ifm-color-primary); + --ifm-background-color: var(--ifm-color-primary-lightest); + --ifm-navbar-background-color: var(--ifm-color-primary-lightest); + --ifm-menu-color: var(--ifm-color-primary-light); + --ifm-code-background-color: var(--ifm-color-primary-lighter); + --ifm-toc-background-color: var(--ifm-color-primary-lightest); + --ifm-links-background-color: var(--ifm-color-primary-lightest); + --ifm-links-hover-background-color: var(--ifm-color-primary-light); + --ifm-color-secondary: var(--ifm-color-primary-light); + --ifm-color-white: var(--ifm-color-primary); +} + +/* For readability concerns, you should choose a lighter palette in dark mode. */ +[data-theme="dark"]:root { + --ifm-color-primary: white; + --ifm-color-primary-dark: #282828; + --ifm-color-primary-darker: #1c1c1c; + --ifm-color-primary-light: #a8a8a8; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + + --ifm-color-content: var(--ifm-color-primary-light); + --ifm-font-color-base: var(--ifm-color-primary-light); + --ifm-heading-color: var(--ifm-color-primary); + --ifm-background-color: var(--ifm-color-primary-dark); + --ifm-navbar-background-color: var(--ifm-color-primary-darker); + --ifm-menu-color: var(--ifm-color-primary-light); + --ifm-code-background-color: var(--ifm-color-primary-darker); + --ifm-toc-background-color: var(--ifm-color-primary-darker); + --ifm-links-background-color: var(--ifm-color-primary-darker); + --ifm-links-hover-background-color: var(--ifm-color-primary-light); +} + +/* Generic */ +body { + margin-left: auto; + margin-right: auto; + max-width: 84rem; + padding-inline: 1rem; +} + +pre code { + background-color: var(--ifm-code-background-color); + border-radius: 1rem; +} + +/* Header */ +.navbar { + top: 1rem; + border-radius: 1rem; + box-shadow: var(--ring-offset-shadow, 0 0 #0000), var(--ring-shadow, 0 0 #0000), var(--shadow); +} + +.navbarHidden_node_modules-\@docusaurus-theme-classic-lib-theme-Navbar-Layout-styles-module { + transform: translate3d(0, calc(-100% - 2px - 1rem), 0) !important; +} + +.container article a { + color: var(--ifm-color-content); + text-decoration: underline; + transition: color 125ms; +} +.container article a:hover { + color: var(--ifm-color-primary); +} + +.navbar__items--right .navbar__link { + margin-block-start: 0.3rem; +} + +/* Main */ +.main-wrapper { + margin-block-start: 2rem; + transition: filter 0.2s ease; + will-change: filter; +} + +@media (min-width: 997px) { + .main-wrapper { + margin-block-start: 0; + } + .main-wrapper .container { + margin-block-start: 4.5rem; + } + + .main-wrapper .container > div > div:first-child { + padding-inline: 4rem 2rem; + } +} + +.navbar-sidebar--show ~ .main-wrapper { + filter: blur(2px); +} + +/* Links */ +[class*="iconExternalLink_node_modules"] { + display: none; +} + +.pagination-nav { + grid-template-columns: repeat(1, 100%); + grid-template-rows: repeat(2, 1fr); +} +.pagination-nav__link--next { + grid-column: 1/2; +} +@media (min-width: 600px) { + .pagination-nav { + grid-template-columns: repeat(2, 1fr); + } + .pagination-nav__link--next { + grid-column: 2/3; + } +} + +.pagination-nav__link { + background-color: var(--ifm-links-background-color); + border-radius: 1rem; + border-color: transparent; + transition: 0.3s ease opacity; + box-shadow: var(--ring-offset-shadow, 0 0 #0000), var(--ring-shadow, 0 0 #0000), var(--shadow); +} + +.pagination-nav__link:hover { + opacity: 0.7; + border-color: transparent; +} + +.pagination-nav__label { + display: flex; + font-size: 1.1rem; +} + +.pagination-nav__link--next .pagination-nav__label { + justify-content: flex-end; +} +.pagination-nav__link { + position: relative; +} +.pagination-nav__link--prev { + padding-inline-start: 3rem; +} +.pagination-nav__link--next { + padding-inline-end: 3rem; +} +.pagination-nav__link--prev .pagination-nav__label::before { + content: "←"; + position: absolute; + display: block; + left: 1rem; +} +.pagination-nav__link--next .pagination-nav__label::after { + content: "→"; + position: absolute; + display: block; + right: 1rem; +} + +.pagination-nav__sublabel { + opacity: 0.7; +} + +/* Sidebar */ +@media (min-width: 997px) { + .theme-doc-sidebar-container { + border-right: none !important; + } +} + +.menu::-webkit-scrollbar { + display: none; +} +.menu { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.theme-doc-sidebar-container > div > div > a { + margin-block-start: 1rem; + margin-block-end: 4rem; +} + +.menu__link--active:not(.menu__link--sublist) { + background-color: transparent; +} + +.menu__link:hover, +.menu__caret:hover, +.menu__list-item-collapsible:hover { + background-color: transparent; + color: var(--ifm-color-primary); +} + +/* Breadcrumb */ +.breadcrumbs__item--active .breadcrumbs__link { + background-color: var(--ifm-navbar-background-color); +} + +/* ToC */ +.theme-doc-toc-desktop { + padding-block-start: 3.5rem; + overflow-y: inherit !important; +} +.table-of-contents { + background-color: var(--ifm-toc-background-color); + border-radius: 1rem; + border: none !important; + box-shadow: var(--ring-offset-shadow, 0 0 #0000), var(--ring-shadow, 0 0 #0000), var(--shadow); +} + +/* Footer */ +.footer { + background-color: transparent; + border-top: 1px solid var(--ifm-menu-color); + margin-block-start: 2rem; + padding-inline: 0.5rem; +} + +.footer .container { + max-width: 100%; +} + +.footer__title { + margin-block-start: 1rem; +} + +@media (min-width: 997px) { + .footer .footer__col:first-child { + max-width: var(--doc-sidebar-width); + } + .footer .footer__col:nth-child(2) { + padding-inline-start: 3.75rem; + } +} + +.gno-footer__socials { + margin-block-start: 1rem; + display: flex; +} + +.gno-footer__socials > a { + margin-inline-end: 0.5rem; +} +.gno-footer__socials > a path { + fill: var(--ifm-links-hover-background-color); + transition: 0.3s ease all; +} +.gno-footer__socials > a:hover path { + fill: var(--ifm-color-primary); +} + +.gno-footer__copy { + font-size: 0.8em; +} diff --git a/misc/docusaurus/static/.nojekyll b/misc/docusaurus/static/.nojekyll new file mode 100644 index 00000000000..e69de29bb2d diff --git a/misc/docusaurus/static/img/favicon.ico b/misc/docusaurus/static/img/favicon.ico new file mode 100644 index 00000000000..78b09c04e8d Binary files /dev/null and b/misc/docusaurus/static/img/favicon.ico differ diff --git a/misc/docusaurus/static/img/logo.svg b/misc/docusaurus/static/img/logo.svg new file mode 100644 index 00000000000..0255c945d25 --- /dev/null +++ b/misc/docusaurus/static/img/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/misc/docusaurus/static/img/logo_light.svg b/misc/docusaurus/static/img/logo_light.svg new file mode 100644 index 00000000000..45cf9c3ada7 --- /dev/null +++ b/misc/docusaurus/static/img/logo_light.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/misc/docusaurus/tsconfig.json b/misc/docusaurus/tsconfig.json new file mode 100644 index 00000000000..6f4756980d4 --- /dev/null +++ b/misc/docusaurus/tsconfig.json @@ -0,0 +1,7 @@ +{ + // This file is not used in compilation. It is here just for a nice editor experience. + "extends": "@tsconfig/docusaurus/tsconfig.json", + "compilerOptions": { + "baseUrl": "." + } +} diff --git a/misc/docusaurus/yarn.lock b/misc/docusaurus/yarn.lock new file mode 100644 index 00000000000..8f58f0af929 --- /dev/null +++ b/misc/docusaurus/yarn.lock @@ -0,0 +1,7726 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@algolia/autocomplete-core@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz#1d56482a768c33aae0868c8533049e02e8961be7" + integrity sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw== + dependencies: + "@algolia/autocomplete-plugin-algolia-insights" "1.9.3" + "@algolia/autocomplete-shared" "1.9.3" + +"@algolia/autocomplete-plugin-algolia-insights@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz#9b7f8641052c8ead6d66c1623d444cbe19dde587" + integrity sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg== + dependencies: + "@algolia/autocomplete-shared" "1.9.3" + +"@algolia/autocomplete-preset-algolia@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz#64cca4a4304cfcad2cf730e83067e0c1b2f485da" + integrity sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA== + dependencies: + "@algolia/autocomplete-shared" "1.9.3" + +"@algolia/autocomplete-shared@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz#2e22e830d36f0a9cf2c0ccd3c7f6d59435b77dfa" + integrity sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ== + +"@algolia/cache-browser-local-storage@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.20.0.tgz#357318242fc542ffce41d6eb5b4a9b402921b0bb" + integrity sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ== + dependencies: + "@algolia/cache-common" "4.20.0" + +"@algolia/cache-common@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.20.0.tgz#ec52230509fce891091ffd0d890618bcdc2fa20d" + integrity sha512-vCfxauaZutL3NImzB2G9LjLt36vKAckc6DhMp05An14kVo8F1Yofb6SIl6U3SaEz8pG2QOB9ptwM5c+zGevwIQ== + +"@algolia/cache-in-memory@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.20.0.tgz#5f18d057bd6b3b075022df085c4f83bcca4e3e67" + integrity sha512-Wm9ak/IaacAZXS4mB3+qF/KCoVSBV6aLgIGFEtQtJwjv64g4ePMapORGmCyulCFwfePaRAtcaTbMcJF+voc/bg== + dependencies: + "@algolia/cache-common" "4.20.0" + +"@algolia/client-account@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.20.0.tgz#23ce0b4cffd63100fb7c1aa1c67a4494de5bd645" + integrity sha512-GGToLQvrwo7am4zVkZTnKa72pheQeez/16sURDWm7Seyz+HUxKi3BM6fthVVPUEBhtJ0reyVtuK9ArmnaKl10Q== + dependencies: + "@algolia/client-common" "4.20.0" + "@algolia/client-search" "4.20.0" + "@algolia/transporter" "4.20.0" + +"@algolia/client-analytics@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.20.0.tgz#0aa6bef35d3a41ac3991b3f46fcd0bf00d276fa9" + integrity sha512-EIr+PdFMOallRdBTHHdKI3CstslgLORQG7844Mq84ib5oVFRVASuuPmG4bXBgiDbcsMLUeOC6zRVJhv1KWI0ug== + dependencies: + "@algolia/client-common" "4.20.0" + "@algolia/client-search" "4.20.0" + "@algolia/requester-common" "4.20.0" + "@algolia/transporter" "4.20.0" + +"@algolia/client-common@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.20.0.tgz#ca60f04466515548651c4371a742fbb8971790ef" + integrity sha512-P3WgMdEss915p+knMMSd/fwiHRHKvDu4DYRrCRaBrsfFw7EQHon+EbRSm4QisS9NYdxbS04kcvNoavVGthyfqQ== + dependencies: + "@algolia/requester-common" "4.20.0" + "@algolia/transporter" "4.20.0" + +"@algolia/client-personalization@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.20.0.tgz#ca81308e8ad0db3b27458b78355f124f29657181" + integrity sha512-N9+zx0tWOQsLc3K4PVRDV8GUeOLAY0i445En79Pr3zWB+m67V+n/8w4Kw1C5LlbHDDJcyhMMIlqezh6BEk7xAQ== + dependencies: + "@algolia/client-common" "4.20.0" + "@algolia/requester-common" "4.20.0" + "@algolia/transporter" "4.20.0" + +"@algolia/client-search@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.20.0.tgz#3bcce817ca6caedc835e0eaf6f580e02ee7c3e15" + integrity sha512-zgwqnMvhWLdpzKTpd3sGmMlr4c+iS7eyyLGiaO51zDZWGMkpgoNVmltkzdBwxOVXz0RsFMznIxB9zuarUv4TZg== + dependencies: + "@algolia/client-common" "4.20.0" + "@algolia/requester-common" "4.20.0" + "@algolia/transporter" "4.20.0" + +"@algolia/events@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" + integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== + +"@algolia/logger-common@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.20.0.tgz#f148ddf67e5d733a06213bebf7117cb8a651ab36" + integrity sha512-xouigCMB5WJYEwvoWW5XDv7Z9f0A8VoXJc3VKwlHJw/je+3p2RcDXfksLI4G4lIVncFUYMZx30tP/rsdlvvzHQ== + +"@algolia/logger-console@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.20.0.tgz#ac443d27c4e94357f3063e675039cef0aa2de0a7" + integrity sha512-THlIGG1g/FS63z0StQqDhT6bprUczBI8wnLT3JWvfAQDZX5P6fCg7dG+pIrUBpDIHGszgkqYEqECaKKsdNKOUA== + dependencies: + "@algolia/logger-common" "4.20.0" + +"@algolia/requester-browser-xhr@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.20.0.tgz#db16d0bdef018b93b51681d3f1e134aca4f64814" + integrity sha512-HbzoSjcjuUmYOkcHECkVTwAelmvTlgs48N6Owt4FnTOQdwn0b8pdht9eMgishvk8+F8bal354nhx/xOoTfwiAw== + dependencies: + "@algolia/requester-common" "4.20.0" + +"@algolia/requester-common@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.20.0.tgz#65694b2263a8712b4360fef18680528ffd435b5c" + integrity sha512-9h6ye6RY/BkfmeJp7Z8gyyeMrmmWsMOCRBXQDs4mZKKsyVlfIVICpcSibbeYcuUdurLhIlrOUkH3rQEgZzonng== + +"@algolia/requester-node-http@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.20.0.tgz#b52b182b52b0b16dec4070832267d484a6b1d5bb" + integrity sha512-ocJ66L60ABSSTRFnCHIEZpNHv6qTxsBwJEPfYaSBsLQodm0F9ptvalFkHMpvj5DfE22oZrcrLbOYM2bdPJRHng== + dependencies: + "@algolia/requester-common" "4.20.0" + +"@algolia/transporter@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.20.0.tgz#7e5b24333d7cc9a926b2f6a249f87c2889b944a9" + integrity sha512-Lsii1pGWOAISbzeyuf+r/GPhvHMPHSPrTDWNcIzOE1SG1inlJHICaVe2ikuoRjcpgxZNU54Jl+if15SUCsaTUg== + dependencies: + "@algolia/cache-common" "4.20.0" + "@algolia/logger-common" "4.20.0" + "@algolia/requester-common" "4.20.0" + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.8.3": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + +"@babel/compat-data@^7.22.20", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" + integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== + +"@babel/core@7.12.9": + version "7.12.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" + integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.5" + "@babel/helper-module-transforms" "^7.12.1" + "@babel/helpers" "^7.12.5" + "@babel/parser" "^7.12.7" + "@babel/template" "^7.12.7" + "@babel/traverse" "^7.12.9" + "@babel/types" "^7.12.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/core@^7.18.6", "@babel/core@^7.19.6": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.0.tgz#f8259ae0e52a123eb40f552551e647b506a94d83" + integrity sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.23.0" + "@babel/helpers" "^7.23.0" + "@babel/parser" "^7.23.0" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.0" + "@babel/types" "^7.23.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.12.5", "@babel/generator@^7.18.7", "@babel/generator@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== + dependencies: + "@babel/types" "^7.23.0" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" + integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.15" + browserslist "^4.21.9" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz#97a61b385e57fe458496fad19f8e63b63c867de4" + integrity sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" + integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz#82c825cadeeeee7aad237618ebbe8fa1710015d7" + integrity sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-member-expression-to-functions@^7.22.15": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" + integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== + dependencies: + "@babel/types" "^7.23.0" + +"@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" + integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-plugin-utils@7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + +"@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" + +"@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" + integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" + integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== + +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== + dependencies: + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.22.19" + +"@babel/helpers@^7.12.5", "@babel/helpers@^7.23.0": + version "7.23.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.1.tgz#44e981e8ce2b9e99f8f0b703f3326a4636c16d15" + integrity sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.0" + "@babel/types" "^7.23.0" + +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.12.7", "@babel/parser@^7.18.8", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz#02dc8a03f613ed5fdc29fb2f728397c78146c962" + integrity sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz#2aeb91d337d4e1a1e7ce85b76a37f5301781200f" + integrity sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.22.15" + +"@babel/plugin-proposal-object-rest-spread@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069" + integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.12.1" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-import-assertions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98" + integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-attributes@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb" + integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz#9d9d357cc818aa7ae7935917c1257f67677a0926" + integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-jsx@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" + integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@7.8.3", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" + integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" + integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-async-generator-functions@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz#3b153af4a6b779f340d5b80d3f634f55820aefa3" + integrity sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-transform-async-to-generator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" + integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== + dependencies: + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.5" + +"@babel/plugin-transform-block-scoped-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" + integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-block-scoping@^7.22.15": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz#8744d02c6c264d82e1a4bc5d2d501fd8aff6f022" + integrity sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77" + integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-static-block@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz#dc8cc6e498f55692ac6b4b89e56d87cec766c974" + integrity sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.11" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-classes@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz#aaf4753aee262a232bbc95451b4bdf9599c65a0b" + integrity sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" + "@babel/helper-split-export-declaration" "^7.22.6" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" + integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/template" "^7.22.5" + +"@babel/plugin-transform-destructuring@^7.22.15": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz#6447aa686be48b32eaf65a73e0e2c0bd010a266c" + integrity sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-dotall-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" + integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-duplicate-keys@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" + integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-dynamic-import@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz#2c7722d2a5c01839eaf31518c6ff96d408e447aa" + integrity sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" + integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-export-namespace-from@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz#b3c84c8f19880b6c7440108f8929caf6056db26c" + integrity sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz#f64b4ccc3a4f131a996388fae7680b472b306b29" + integrity sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" + integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== + dependencies: + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-json-strings@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz#689a34e1eed1928a40954e37f74509f48af67835" + integrity sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-transform-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" + integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-logical-assignment-operators@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz#24c522a61688bde045b7d9bc3c2597a4d948fc9c" + integrity sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-transform-member-expression-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" + integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-modules-amd@^7.22.5": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz#05b2bc43373faa6d30ca89214731f76f966f3b88" + integrity sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw== + dependencies: + "@babel/helper-module-transforms" "^7.23.0" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-modules-commonjs@^7.22.15", "@babel/plugin-transform-modules-commonjs@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz#b3dba4757133b2762c00f4f94590cf6d52602481" + integrity sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ== + dependencies: + "@babel/helper-module-transforms" "^7.23.0" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + +"@babel/plugin-transform-modules-systemjs@^7.22.11": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz#77591e126f3ff4132a40595a6cccd00a6b60d160" + integrity sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg== + dependencies: + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-module-transforms" "^7.23.0" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/plugin-transform-modules-umd@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" + integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ== + dependencies: + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" + integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-new-target@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" + integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz#debef6c8ba795f5ac67cd861a81b744c5d38d9fc" + integrity sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-numeric-separator@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz#498d77dc45a6c6db74bb829c02a01c1d719cbfbd" + integrity sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-object-rest-spread@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz#21a95db166be59b91cde48775310c0df6e1da56f" + integrity sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.22.15" + +"@babel/plugin-transform-object-super@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" + integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.5" + +"@babel/plugin-transform-optional-catch-binding@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz#461cc4f578a127bb055527b3e77404cad38c08e0" + integrity sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.22.15": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz#73ff5fc1cf98f542f09f29c0631647d8ad0be158" + integrity sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz#719ca82a01d177af358df64a514d64c2e3edb114" + integrity sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-private-methods@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722" + integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-private-property-in-object@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz#ad45c4fc440e9cb84c718ed0906d96cf40f9a4e1" + integrity sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.11" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-transform-property-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" + integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-constant-elements@^7.18.12": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz#6dfa7c1c37f7d7279e417ceddf5a04abb8bb9c29" + integrity sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-display-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz#3c4326f9fce31c7968d6cb9debcaf32d9e279a2b" + integrity sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-jsx-development@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87" + integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.22.5" + +"@babel/plugin-transform-react-jsx@^7.22.15", "@babel/plugin-transform-react-jsx@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz#7e6266d88705d7c49f11c98db8b9464531289cd6" + integrity sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.22.5" + "@babel/types" "^7.22.15" + +"@babel/plugin-transform-react-pure-annotations@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz#1f58363eef6626d6fa517b95ac66fe94685e32c0" + integrity sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-regenerator@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz#8ceef3bd7375c4db7652878b0241b2be5d0c3cca" + integrity sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-reserved-words@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" + integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-runtime@^7.18.6": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.15.tgz#3a625c4c05a39e932d7d34f5d4895cdd0172fdc9" + integrity sha512-tEVLhk8NRZSmwQ0DJtxxhTrCht1HVo8VaMzYT4w6lwyKBuHsgoioAUA7/6eT2fRfc5/23fuGdlwIxXhRVgWr4g== + dependencies: + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + babel-plugin-polyfill-corejs2 "^0.4.5" + babel-plugin-polyfill-corejs3 "^0.8.3" + babel-plugin-polyfill-regenerator "^0.5.2" + semver "^6.3.1" + +"@babel/plugin-transform-shorthand-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" + integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-spread@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" + integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-sticky-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" + integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-template-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" + integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-typeof-symbol@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" + integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-typescript@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz#15adef906451d86349eb4b8764865c960eb54127" + integrity sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-typescript" "^7.22.5" + +"@babel/plugin-transform-unicode-escapes@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz#c723f380f40a2b2f57a62df24c9005834c8616d9" + integrity sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-property-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81" + integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" + integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-sets-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91" + integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/preset-env@^7.18.6", "@babel/preset-env@^7.19.4": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.20.tgz#de9e9b57e1127ce0a2f580831717f7fb677ceedb" + integrity sha512-11MY04gGC4kSzlPHRfvVkNAZhUxOvm7DCJ37hPDnUENwe06npjIRAfInEMTGSb4LZK5ZgDFkv5hw0lGebHeTyg== + dependencies: + "@babel/compat-data" "^7.22.20" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.15" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.15" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.22.5" + "@babel/plugin-syntax-import-attributes" "^7.22.5" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.22.5" + "@babel/plugin-transform-async-generator-functions" "^7.22.15" + "@babel/plugin-transform-async-to-generator" "^7.22.5" + "@babel/plugin-transform-block-scoped-functions" "^7.22.5" + "@babel/plugin-transform-block-scoping" "^7.22.15" + "@babel/plugin-transform-class-properties" "^7.22.5" + "@babel/plugin-transform-class-static-block" "^7.22.11" + "@babel/plugin-transform-classes" "^7.22.15" + "@babel/plugin-transform-computed-properties" "^7.22.5" + "@babel/plugin-transform-destructuring" "^7.22.15" + "@babel/plugin-transform-dotall-regex" "^7.22.5" + "@babel/plugin-transform-duplicate-keys" "^7.22.5" + "@babel/plugin-transform-dynamic-import" "^7.22.11" + "@babel/plugin-transform-exponentiation-operator" "^7.22.5" + "@babel/plugin-transform-export-namespace-from" "^7.22.11" + "@babel/plugin-transform-for-of" "^7.22.15" + "@babel/plugin-transform-function-name" "^7.22.5" + "@babel/plugin-transform-json-strings" "^7.22.11" + "@babel/plugin-transform-literals" "^7.22.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.22.11" + "@babel/plugin-transform-member-expression-literals" "^7.22.5" + "@babel/plugin-transform-modules-amd" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.15" + "@babel/plugin-transform-modules-systemjs" "^7.22.11" + "@babel/plugin-transform-modules-umd" "^7.22.5" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.22.5" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11" + "@babel/plugin-transform-numeric-separator" "^7.22.11" + "@babel/plugin-transform-object-rest-spread" "^7.22.15" + "@babel/plugin-transform-object-super" "^7.22.5" + "@babel/plugin-transform-optional-catch-binding" "^7.22.11" + "@babel/plugin-transform-optional-chaining" "^7.22.15" + "@babel/plugin-transform-parameters" "^7.22.15" + "@babel/plugin-transform-private-methods" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.11" + "@babel/plugin-transform-property-literals" "^7.22.5" + "@babel/plugin-transform-regenerator" "^7.22.10" + "@babel/plugin-transform-reserved-words" "^7.22.5" + "@babel/plugin-transform-shorthand-properties" "^7.22.5" + "@babel/plugin-transform-spread" "^7.22.5" + "@babel/plugin-transform-sticky-regex" "^7.22.5" + "@babel/plugin-transform-template-literals" "^7.22.5" + "@babel/plugin-transform-typeof-symbol" "^7.22.5" + "@babel/plugin-transform-unicode-escapes" "^7.22.10" + "@babel/plugin-transform-unicode-property-regex" "^7.22.5" + "@babel/plugin-transform-unicode-regex" "^7.22.5" + "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" + "@babel/preset-modules" "0.1.6-no-external-plugins" + "@babel/types" "^7.22.19" + babel-plugin-polyfill-corejs2 "^0.4.5" + babel-plugin-polyfill-corejs3 "^0.8.3" + babel-plugin-polyfill-regenerator "^0.5.2" + core-js-compat "^3.31.0" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@^7.18.6": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.15.tgz#9a776892b648e13cc8ca2edf5ed1264eea6b6afc" + integrity sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-transform-react-display-name" "^7.22.5" + "@babel/plugin-transform-react-jsx" "^7.22.15" + "@babel/plugin-transform-react-jsx-development" "^7.22.5" + "@babel/plugin-transform-react-pure-annotations" "^7.22.5" + +"@babel/preset-typescript@^7.18.6": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.23.0.tgz#cc6602d13e7e5b2087c811912b87cf937a9129d9" + integrity sha512-6P6VVa/NM/VlAYj5s2Aq/gdVg8FSENCg3wlZ6Qau9AcPaoF5LbN1nyGlR9DTRIw9PpxI94e+ReydsJHcjwAweg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-syntax-jsx" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.23.0" + "@babel/plugin-transform-typescript" "^7.22.15" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== + +"@babel/runtime-corejs3@^7.18.6": + version "7.23.1" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.23.1.tgz#d03f5819f4ba81a21dd1f80edfb19983e9e20fc1" + integrity sha512-OKKfytwoc0tr7cDHwQm0RLVR3y+hDGFz3EPuvLNU/0fOeXJeKNIHj7ffNVFnncWt3sC58uyUCRSzf8nBQbyF6A== + dependencies: + core-js-pure "^3.30.2" + regenerator-runtime "^0.14.0" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.8.4": + version "7.23.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.1.tgz#72741dc4d413338a91dcb044a86f3c0bc402646d" + integrity sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.12.7", "@babel/template@^7.22.15", "@babel/template@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53" + integrity sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.12.7", "@babel/types@^7.20.0", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.4.4": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@discoveryjs/json-ext@0.5.7": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@docsearch/css@3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.5.2.tgz#610f47b48814ca94041df969d9fcc47b91fc5aac" + integrity sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA== + +"@docsearch/react@^3.1.1": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.5.2.tgz#2e6bbee00eb67333b64906352734da6aef1232b9" + integrity sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng== + dependencies: + "@algolia/autocomplete-core" "1.9.3" + "@algolia/autocomplete-preset-algolia" "1.9.3" + "@docsearch/css" "3.5.2" + algoliasearch "^4.19.1" + +"@docusaurus/core@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.4.1.tgz#4b8ff5766131ce3fbccaad0b1daf2ad4dc76f62d" + integrity sha512-SNsY7PshK3Ri7vtsLXVeAJGS50nJN3RgF836zkyUfAD01Fq+sAk5EwWgLw+nnm5KVNGDu7PRR2kRGDsWvqpo0g== + dependencies: + "@babel/core" "^7.18.6" + "@babel/generator" "^7.18.7" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-runtime" "^7.18.6" + "@babel/preset-env" "^7.18.6" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.18.6" + "@babel/runtime" "^7.18.6" + "@babel/runtime-corejs3" "^7.18.6" + "@babel/traverse" "^7.18.8" + "@docusaurus/cssnano-preset" "2.4.1" + "@docusaurus/logger" "2.4.1" + "@docusaurus/mdx-loader" "2.4.1" + "@docusaurus/react-loadable" "5.5.2" + "@docusaurus/utils" "2.4.1" + "@docusaurus/utils-common" "2.4.1" + "@docusaurus/utils-validation" "2.4.1" + "@slorber/static-site-generator-webpack-plugin" "^4.0.7" + "@svgr/webpack" "^6.2.1" + autoprefixer "^10.4.7" + babel-loader "^8.2.5" + babel-plugin-dynamic-import-node "^2.3.3" + boxen "^6.2.1" + chalk "^4.1.2" + chokidar "^3.5.3" + clean-css "^5.3.0" + cli-table3 "^0.6.2" + combine-promises "^1.1.0" + commander "^5.1.0" + copy-webpack-plugin "^11.0.0" + core-js "^3.23.3" + css-loader "^6.7.1" + css-minimizer-webpack-plugin "^4.0.0" + cssnano "^5.1.12" + del "^6.1.1" + detect-port "^1.3.0" + escape-html "^1.0.3" + eta "^2.0.0" + file-loader "^6.2.0" + fs-extra "^10.1.0" + html-minifier-terser "^6.1.0" + html-tags "^3.2.0" + html-webpack-plugin "^5.5.0" + import-fresh "^3.3.0" + leven "^3.1.0" + lodash "^4.17.21" + mini-css-extract-plugin "^2.6.1" + postcss "^8.4.14" + postcss-loader "^7.0.0" + prompts "^2.4.2" + react-dev-utils "^12.0.1" + react-helmet-async "^1.3.0" + react-loadable "npm:@docusaurus/react-loadable@5.5.2" + react-loadable-ssr-addon-v5-slorber "^1.0.1" + react-router "^5.3.3" + react-router-config "^5.1.1" + react-router-dom "^5.3.3" + rtl-detect "^1.0.4" + semver "^7.3.7" + serve-handler "^6.1.3" + shelljs "^0.8.5" + terser-webpack-plugin "^5.3.3" + tslib "^2.4.0" + update-notifier "^5.1.0" + url-loader "^4.1.1" + wait-on "^6.0.1" + webpack "^5.73.0" + webpack-bundle-analyzer "^4.5.0" + webpack-dev-server "^4.9.3" + webpack-merge "^5.8.0" + webpackbar "^5.0.2" + +"@docusaurus/cssnano-preset@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.1.tgz#eacadefb1e2e0f59df3467a0fe83e4ff79eed163" + integrity sha512-ka+vqXwtcW1NbXxWsh6yA1Ckii1klY9E53cJ4O9J09nkMBgrNX3iEFED1fWdv8wf4mJjvGi5RLZ2p9hJNjsLyQ== + dependencies: + cssnano-preset-advanced "^5.3.8" + postcss "^8.4.14" + postcss-sort-media-queries "^4.2.1" + tslib "^2.4.0" + +"@docusaurus/logger@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.4.1.tgz#4d2c0626b40752641f9fdd93ad9b5a7a0792f767" + integrity sha512-5h5ysIIWYIDHyTVd8BjheZmQZmEgWDR54aQ1BX9pjFfpyzFo5puKXKYrYJXbjEHGyVhEzmB9UXwbxGfaZhOjcg== + dependencies: + chalk "^4.1.2" + tslib "^2.4.0" + +"@docusaurus/mdx-loader@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.4.1.tgz#6425075d7fc136dbfdc121349060cedd64118393" + integrity sha512-4KhUhEavteIAmbBj7LVFnrVYDiU51H5YWW1zY6SmBSte/YLhDutztLTBE0PQl1Grux1jzUJeaSvAzHpTn6JJDQ== + dependencies: + "@babel/parser" "^7.18.8" + "@babel/traverse" "^7.18.8" + "@docusaurus/logger" "2.4.1" + "@docusaurus/utils" "2.4.1" + "@mdx-js/mdx" "^1.6.22" + escape-html "^1.0.3" + file-loader "^6.2.0" + fs-extra "^10.1.0" + image-size "^1.0.1" + mdast-util-to-string "^2.0.0" + remark-emoji "^2.2.0" + stringify-object "^3.3.0" + tslib "^2.4.0" + unified "^9.2.2" + unist-util-visit "^2.0.3" + url-loader "^4.1.1" + webpack "^5.73.0" + +"@docusaurus/module-type-aliases@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.1.tgz#38b3c2d2ae44bea6d57506eccd84280216f0171c" + integrity sha512-gLBuIFM8Dp2XOCWffUDSjtxY7jQgKvYujt7Mx5s4FCTfoL5dN1EVbnrn+O2Wvh8b0a77D57qoIDY7ghgmatR1A== + dependencies: + "@docusaurus/react-loadable" "5.5.2" + "@docusaurus/types" "2.4.1" + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router-config" "*" + "@types/react-router-dom" "*" + react-helmet-async "*" + react-loadable "npm:@docusaurus/react-loadable@5.5.2" + +"@docusaurus/plugin-content-blog@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.1.tgz#c705a8b1a36a34f181dcf43b7770532e4dcdc4a3" + integrity sha512-E2i7Knz5YIbE1XELI6RlTnZnGgS52cUO4BlCiCUCvQHbR+s1xeIWz4C6BtaVnlug0Ccz7nFSksfwDpVlkujg5Q== + dependencies: + "@docusaurus/core" "2.4.1" + "@docusaurus/logger" "2.4.1" + "@docusaurus/mdx-loader" "2.4.1" + "@docusaurus/types" "2.4.1" + "@docusaurus/utils" "2.4.1" + "@docusaurus/utils-common" "2.4.1" + "@docusaurus/utils-validation" "2.4.1" + cheerio "^1.0.0-rc.12" + feed "^4.2.2" + fs-extra "^10.1.0" + lodash "^4.17.21" + reading-time "^1.5.0" + tslib "^2.4.0" + unist-util-visit "^2.0.3" + utility-types "^3.10.0" + webpack "^5.73.0" + +"@docusaurus/plugin-content-docs@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.1.tgz#ed94d9721b5ce7a956fb01cc06c40d8eee8dfca7" + integrity sha512-Lo7lSIcpswa2Kv4HEeUcGYqaasMUQNpjTXpV0N8G6jXgZaQurqp7E8NGYeGbDXnb48czmHWbzDL4S3+BbK0VzA== + dependencies: + "@docusaurus/core" "2.4.1" + "@docusaurus/logger" "2.4.1" + "@docusaurus/mdx-loader" "2.4.1" + "@docusaurus/module-type-aliases" "2.4.1" + "@docusaurus/types" "2.4.1" + "@docusaurus/utils" "2.4.1" + "@docusaurus/utils-validation" "2.4.1" + "@types/react-router-config" "^5.0.6" + combine-promises "^1.1.0" + fs-extra "^10.1.0" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + lodash "^4.17.21" + tslib "^2.4.0" + utility-types "^3.10.0" + webpack "^5.73.0" + +"@docusaurus/plugin-content-pages@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.1.tgz#c534f7e49967699a45bbe67050d1605ebbf3d285" + integrity sha512-/UjuH/76KLaUlL+o1OvyORynv6FURzjurSjvn2lbWTFc4tpYY2qLYTlKpTCBVPhlLUQsfyFnshEJDLmPneq2oA== + dependencies: + "@docusaurus/core" "2.4.1" + "@docusaurus/mdx-loader" "2.4.1" + "@docusaurus/types" "2.4.1" + "@docusaurus/utils" "2.4.1" + "@docusaurus/utils-validation" "2.4.1" + fs-extra "^10.1.0" + tslib "^2.4.0" + webpack "^5.73.0" + +"@docusaurus/plugin-debug@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.4.1.tgz#461a2c77b0c5a91b2c05257c8f9585412aaa59dc" + integrity sha512-7Yu9UPzRShlrH/G8btOpR0e6INFZr0EegWplMjOqelIwAcx3PKyR8mgPTxGTxcqiYj6hxSCRN0D8R7YrzImwNA== + dependencies: + "@docusaurus/core" "2.4.1" + "@docusaurus/types" "2.4.1" + "@docusaurus/utils" "2.4.1" + fs-extra "^10.1.0" + react-json-view "^1.21.3" + tslib "^2.4.0" + +"@docusaurus/plugin-google-analytics@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.1.tgz#30de1c35773bf9d52bb2d79b201b23eb98022613" + integrity sha512-dyZJdJiCoL+rcfnm0RPkLt/o732HvLiEwmtoNzOoz9MSZz117UH2J6U2vUDtzUzwtFLIf32KkeyzisbwUCgcaQ== + dependencies: + "@docusaurus/core" "2.4.1" + "@docusaurus/types" "2.4.1" + "@docusaurus/utils-validation" "2.4.1" + tslib "^2.4.0" + +"@docusaurus/plugin-google-gtag@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.1.tgz#6a3eb91022714735e625c7ca70ef5188fa7bd0dc" + integrity sha512-mKIefK+2kGTQBYvloNEKtDmnRD7bxHLsBcxgnbt4oZwzi2nxCGjPX6+9SQO2KCN5HZbNrYmGo5GJfMgoRvy6uA== + dependencies: + "@docusaurus/core" "2.4.1" + "@docusaurus/types" "2.4.1" + "@docusaurus/utils-validation" "2.4.1" + tslib "^2.4.0" + +"@docusaurus/plugin-google-tag-manager@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-2.4.1.tgz#b99f71aec00b112bbf509ef2416e404a95eb607e" + integrity sha512-Zg4Ii9CMOLfpeV2nG74lVTWNtisFaH9QNtEw48R5QE1KIwDBdTVaiSA18G1EujZjrzJJzXN79VhINSbOJO/r3g== + dependencies: + "@docusaurus/core" "2.4.1" + "@docusaurus/types" "2.4.1" + "@docusaurus/utils-validation" "2.4.1" + tslib "^2.4.0" + +"@docusaurus/plugin-sitemap@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.4.1.tgz#8a7a76ed69dc3e6b4474b6abb10bb03336a9de6d" + integrity sha512-lZx+ijt/+atQ3FVE8FOHV/+X3kuok688OydDXrqKRJyXBJZKgGjA2Qa8RjQ4f27V2woaXhtnyrdPop/+OjVMRg== + dependencies: + "@docusaurus/core" "2.4.1" + "@docusaurus/logger" "2.4.1" + "@docusaurus/types" "2.4.1" + "@docusaurus/utils" "2.4.1" + "@docusaurus/utils-common" "2.4.1" + "@docusaurus/utils-validation" "2.4.1" + fs-extra "^10.1.0" + sitemap "^7.1.1" + tslib "^2.4.0" + +"@docusaurus/preset-classic@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.4.1.tgz#072f22d0332588e9c5f512d4bded8d7c99f91497" + integrity sha512-P4//+I4zDqQJ+UDgoFrjIFaQ1MeS9UD1cvxVQaI6O7iBmiHQm0MGROP1TbE7HlxlDPXFJjZUK3x3cAoK63smGQ== + dependencies: + "@docusaurus/core" "2.4.1" + "@docusaurus/plugin-content-blog" "2.4.1" + "@docusaurus/plugin-content-docs" "2.4.1" + "@docusaurus/plugin-content-pages" "2.4.1" + "@docusaurus/plugin-debug" "2.4.1" + "@docusaurus/plugin-google-analytics" "2.4.1" + "@docusaurus/plugin-google-gtag" "2.4.1" + "@docusaurus/plugin-google-tag-manager" "2.4.1" + "@docusaurus/plugin-sitemap" "2.4.1" + "@docusaurus/theme-classic" "2.4.1" + "@docusaurus/theme-common" "2.4.1" + "@docusaurus/theme-search-algolia" "2.4.1" + "@docusaurus/types" "2.4.1" + +"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" + integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== + dependencies: + "@types/react" "*" + prop-types "^15.6.2" + +"@docusaurus/theme-classic@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.4.1.tgz#0060cb263c1a73a33ac33f79bb6bc2a12a56ad9e" + integrity sha512-Rz0wKUa+LTW1PLXmwnf8mn85EBzaGSt6qamqtmnh9Hflkc+EqiYMhtUJeLdV+wsgYq4aG0ANc+bpUDpsUhdnwg== + dependencies: + "@docusaurus/core" "2.4.1" + "@docusaurus/mdx-loader" "2.4.1" + "@docusaurus/module-type-aliases" "2.4.1" + "@docusaurus/plugin-content-blog" "2.4.1" + "@docusaurus/plugin-content-docs" "2.4.1" + "@docusaurus/plugin-content-pages" "2.4.1" + "@docusaurus/theme-common" "2.4.1" + "@docusaurus/theme-translations" "2.4.1" + "@docusaurus/types" "2.4.1" + "@docusaurus/utils" "2.4.1" + "@docusaurus/utils-common" "2.4.1" + "@docusaurus/utils-validation" "2.4.1" + "@mdx-js/react" "^1.6.22" + clsx "^1.2.1" + copy-text-to-clipboard "^3.0.1" + infima "0.2.0-alpha.43" + lodash "^4.17.21" + nprogress "^0.2.0" + postcss "^8.4.14" + prism-react-renderer "^1.3.5" + prismjs "^1.28.0" + react-router-dom "^5.3.3" + rtlcss "^3.5.0" + tslib "^2.4.0" + utility-types "^3.10.0" + +"@docusaurus/theme-common@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.4.1.tgz#03e16f7aa96455e952f3243ac99757b01a3c83d4" + integrity sha512-G7Zau1W5rQTaFFB3x3soQoZpkgMbl/SYNG8PfMFIjKa3M3q8n0m/GRf5/H/e5BqOvt8c+ZWIXGCiz+kUCSHovA== + dependencies: + "@docusaurus/mdx-loader" "2.4.1" + "@docusaurus/module-type-aliases" "2.4.1" + "@docusaurus/plugin-content-blog" "2.4.1" + "@docusaurus/plugin-content-docs" "2.4.1" + "@docusaurus/plugin-content-pages" "2.4.1" + "@docusaurus/utils" "2.4.1" + "@docusaurus/utils-common" "2.4.1" + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router-config" "*" + clsx "^1.2.1" + parse-numeric-range "^1.3.0" + prism-react-renderer "^1.3.5" + tslib "^2.4.0" + use-sync-external-store "^1.2.0" + utility-types "^3.10.0" + +"@docusaurus/theme-search-algolia@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.1.tgz#906bd2cca3fced0241985ef502c892f58ff380fc" + integrity sha512-6BcqW2lnLhZCXuMAvPRezFs1DpmEKzXFKlYjruuas+Xy3AQeFzDJKTJFIm49N77WFCTyxff8d3E4Q9pi/+5McQ== + dependencies: + "@docsearch/react" "^3.1.1" + "@docusaurus/core" "2.4.1" + "@docusaurus/logger" "2.4.1" + "@docusaurus/plugin-content-docs" "2.4.1" + "@docusaurus/theme-common" "2.4.1" + "@docusaurus/theme-translations" "2.4.1" + "@docusaurus/utils" "2.4.1" + "@docusaurus/utils-validation" "2.4.1" + algoliasearch "^4.13.1" + algoliasearch-helper "^3.10.0" + clsx "^1.2.1" + eta "^2.0.0" + fs-extra "^10.1.0" + lodash "^4.17.21" + tslib "^2.4.0" + utility-types "^3.10.0" + +"@docusaurus/theme-translations@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.4.1.tgz#4d49df5865dae9ef4b98a19284ede62ae6f98726" + integrity sha512-T1RAGP+f86CA1kfE8ejZ3T3pUU3XcyvrGMfC/zxCtc2BsnoexuNI9Vk2CmuKCb+Tacvhxjv5unhxXce0+NKyvA== + dependencies: + fs-extra "^10.1.0" + tslib "^2.4.0" + +"@docusaurus/types@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.4.1.tgz#d8e82f9e0f704984f98df1f93d6b4554d5458705" + integrity sha512-0R+cbhpMkhbRXX138UOc/2XZFF8hiZa6ooZAEEJFp5scytzCw4tC1gChMFXrpa3d2tYE6AX8IrOEpSonLmfQuQ== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + commander "^5.1.0" + joi "^17.6.0" + react-helmet-async "^1.3.0" + utility-types "^3.10.0" + webpack "^5.73.0" + webpack-merge "^5.8.0" + +"@docusaurus/utils-common@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.4.1.tgz#7f72e873e49bd5179588869cc3ab7449a56aae63" + integrity sha512-bCVGdZU+z/qVcIiEQdyx0K13OC5mYwxhSuDUR95oFbKVuXYRrTVrwZIqQljuo1fyJvFTKHiL9L9skQOPokuFNQ== + dependencies: + tslib "^2.4.0" + +"@docusaurus/utils-validation@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.4.1.tgz#19959856d4a886af0c5cfb357f4ef68b51151244" + integrity sha512-unII3hlJlDwZ3w8U+pMO3Lx3RhI4YEbY3YNsQj4yzrkZzlpqZOLuAiZK2JyULnD+TKbceKU0WyWkQXtYbLNDFA== + dependencies: + "@docusaurus/logger" "2.4.1" + "@docusaurus/utils" "2.4.1" + joi "^17.6.0" + js-yaml "^4.1.0" + tslib "^2.4.0" + +"@docusaurus/utils@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.4.1.tgz#9c5f76eae37b71f3819c1c1f0e26e6807c99a4fc" + integrity sha512-1lvEZdAQhKNht9aPXPoh69eeKnV0/62ROhQeFKKxmzd0zkcuE/Oc5Gpnt00y/f5bIsmOsYMY7Pqfm/5rteT5GA== + dependencies: + "@docusaurus/logger" "2.4.1" + "@svgr/webpack" "^6.2.1" + escape-string-regexp "^4.0.0" + file-loader "^6.2.0" + fs-extra "^10.1.0" + github-slugger "^1.4.0" + globby "^11.1.0" + gray-matter "^4.0.3" + js-yaml "^4.1.0" + lodash "^4.17.21" + micromatch "^4.0.5" + resolve-pathname "^3.0.0" + shelljs "^0.8.5" + tslib "^2.4.0" + url-loader "^4.1.1" + webpack "^5.73.0" + +"@hapi/hoek@^9.0.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.3": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" + integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.19" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" + integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" + integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + +"@mdx-js/mdx@^1.6.22": + version "1.6.22" + resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" + integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA== + dependencies: + "@babel/core" "7.12.9" + "@babel/plugin-syntax-jsx" "7.12.1" + "@babel/plugin-syntax-object-rest-spread" "7.8.3" + "@mdx-js/util" "1.6.22" + babel-plugin-apply-mdx-type-prop "1.6.22" + babel-plugin-extract-import-names "1.6.22" + camelcase-css "2.0.1" + detab "2.0.4" + hast-util-raw "6.0.1" + lodash.uniq "4.5.0" + mdast-util-to-hast "10.0.1" + remark-footnotes "2.0.0" + remark-mdx "1.6.22" + remark-parse "8.0.3" + remark-squeeze-paragraphs "4.0.0" + style-to-object "0.3.0" + unified "9.2.0" + unist-builder "2.0.3" + unist-util-visit "2.0.3" + +"@mdx-js/react@^1.6.22": + version "1.6.22" + resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" + integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== + +"@mdx-js/util@1.6.22": + version "1.6.22" + resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" + integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@polka/url@^1.0.0-next.20": + version "1.0.0-next.23" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.23.tgz#498e41218ab3b6a1419c735e5c6ae2c5ed609b6c" + integrity sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg== + +"@sideway/address@^4.1.3": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" + integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@slorber/static-site-generator-webpack-plugin@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.7.tgz#fc1678bddefab014e2145cbe25b3ce4e1cfc36f3" + integrity sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA== + dependencies: + eval "^0.1.8" + p-map "^4.0.0" + webpack-sources "^3.2.2" + +"@svgr/babel-plugin-add-jsx-attribute@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz#74a5d648bd0347bda99d82409d87b8ca80b9a1ba" + integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ== + +"@svgr/babel-plugin-remove-jsx-attribute@*": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + +"@svgr/babel-plugin-remove-jsx-empty-expression@*": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz#fb9d22ea26d2bc5e0a44b763d4c46d5d3f596c60" + integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== + +"@svgr/babel-plugin-svg-dynamic-title@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz#01b2024a2b53ffaa5efceaa0bf3e1d5a4c520ce4" + integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== + +"@svgr/babel-plugin-svg-em-dimensions@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz#dd3fa9f5b24eb4f93bcf121c3d40ff5facecb217" + integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== + +"@svgr/babel-plugin-transform-react-native-svg@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz#1d8e945a03df65b601551097d8f5e34351d3d305" + integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== + +"@svgr/babel-plugin-transform-svg-component@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz#48620b9e590e25ff95a80f811544218d27f8a250" + integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== + +"@svgr/babel-preset@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.5.1.tgz#b90de7979c8843c5c580c7e2ec71f024b49eb828" + integrity sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^6.5.1" + "@svgr/babel-plugin-remove-jsx-attribute" "*" + "@svgr/babel-plugin-remove-jsx-empty-expression" "*" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^6.5.1" + "@svgr/babel-plugin-svg-dynamic-title" "^6.5.1" + "@svgr/babel-plugin-svg-em-dimensions" "^6.5.1" + "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1" + "@svgr/babel-plugin-transform-svg-component" "^6.5.1" + +"@svgr/core@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.5.1.tgz#d3e8aa9dbe3fbd747f9ee4282c1c77a27410488a" + integrity sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw== + dependencies: + "@babel/core" "^7.19.6" + "@svgr/babel-preset" "^6.5.1" + "@svgr/plugin-jsx" "^6.5.1" + camelcase "^6.2.0" + cosmiconfig "^7.0.1" + +"@svgr/hast-util-to-babel-ast@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz#81800bd09b5bcdb968bf6ee7c863d2288fdb80d2" + integrity sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw== + dependencies: + "@babel/types" "^7.20.0" + entities "^4.4.0" + +"@svgr/plugin-jsx@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz#0e30d1878e771ca753c94e69581c7971542a7072" + integrity sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw== + dependencies: + "@babel/core" "^7.19.6" + "@svgr/babel-preset" "^6.5.1" + "@svgr/hast-util-to-babel-ast" "^6.5.1" + svg-parser "^2.0.4" + +"@svgr/plugin-svgo@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz#0f91910e988fc0b842f88e0960c2862e022abe84" + integrity sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ== + dependencies: + cosmiconfig "^7.0.1" + deepmerge "^4.2.2" + svgo "^2.8.0" + +"@svgr/webpack@^6.2.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.5.1.tgz#ecf027814fc1cb2decc29dc92f39c3cf691e40e8" + integrity sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA== + dependencies: + "@babel/core" "^7.19.6" + "@babel/plugin-transform-react-constant-elements" "^7.18.12" + "@babel/preset-env" "^7.19.4" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.18.6" + "@svgr/core" "^6.5.1" + "@svgr/plugin-jsx" "^6.5.1" + "@svgr/plugin-svgo" "^6.5.1" + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@tsconfig/docusaurus@^1.0.5": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@tsconfig/docusaurus/-/docusaurus-1.0.7.tgz#a3ee3c8109b3fec091e3d61a61834e563aeee3c3" + integrity sha512-ffTXxGIP/IRMCjuzHd6M4/HdIrw1bMfC7Bv8hMkTadnePkpe0lG0oDSdbRpSDZb2rQMAgpbWiR10BvxvNYwYrg== + +"@types/body-parser@*": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.3.tgz#fb558014374f7d9e56c8f34bab2042a3a07d25cd" + integrity sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.11" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.11.tgz#fbaa46a1529ea5c5e46cde36e4be6a880db55b84" + integrity sha512-isGhjmBtLIxdHBDl2xGwUzEM8AOyOvWsADWq7rqirdi/ZQoHnLWErHvsThcEzTX8juDRiZtzp2Qkv5bgNh6mAg== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.1.tgz#6e5e3602d93bda975cebc3449e1a318340af9e20" + integrity sha512-iaQslNbARe8fctL5Lk+DsmgWOM83lM+7FzP0eQUJs1jd3kBE8NWqBTIT2S8SqQOJjxvt2eyIjpOuYeRXq2AdMw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.36" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.36.tgz#e511558c15a39cb29bd5357eebb57bd1459cd1ab" + integrity sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w== + dependencies: + "@types/node" "*" + +"@types/eslint-scope@^3.7.3": + version "3.7.5" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.5.tgz#e28b09dbb1d9d35fdfa8a884225f00440dfc5a3e" + integrity sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.44.3" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.3.tgz#96614fae4875ea6328f56de38666f582d911d962" + integrity sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.2.tgz#ff02bc3dc8317cd668dfec247b750ba1f1d62453" + integrity sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": + version "4.17.37" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz#7e4b7b59da9142138a2aaa7621f5abedce8c7320" + integrity sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.18" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.18.tgz#efabf5c4495c1880df1bdffee604b143b29c4a95" + integrity sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/hast@^2.0.0": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.6.tgz#bb8b05602112a26d22868acb70c4b20984ec7086" + integrity sha512-47rJE80oqPmFdVDCD7IheXBrVdwuBgsYwoczFvKmwfo2Mzsnt+V9OONsYauFmICb6lQPpCuXYJWejBNs4pDJRg== + dependencies: + "@types/unist" "^2" + +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== + +"@types/html-minifier-terser@^6.0.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" + integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== + +"@types/http-errors@*": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.2.tgz#a86e00bbde8950364f8e7846687259ffcd96e8c2" + integrity sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg== + +"@types/http-proxy@^1.17.8": + version "1.17.12" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.12.tgz#86e849e9eeae0362548803c37a0a1afc616bd96b" + integrity sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#412e0725ef41cde73bfa03e0e833eaff41e0fd63" + integrity sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz#edc8e421991a3b4df875036d381fc0a5a982f549" + integrity sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== + +"@types/mdast@^3.0.0": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.13.tgz#b7ba6e52d0faeb9c493e32c205f3831022be4e1b" + integrity sha512-HjiGiWedR0DVFkeNljpa6Lv4/IZU1+30VY5d747K7lBudFc3R0Ibr6yJ9lN3BE28VnZyDfLF/VB1Ql1ZIbKrmg== + dependencies: + "@types/unist" "^2" + +"@types/mime@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.2.tgz#c1ae807f13d308ee7511a5b81c74f327028e66e8" + integrity sha512-Wj+fqpTLtTbG7c0tH47dkahefpLKEbB+xAZuLq7b4/IDHPl/n6VoXcyUQ2bypFlbSwvCr0y+bD4euTTqTJsPxQ== + +"@types/mime@^1": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.3.tgz#bbe64987e0eb05de150c305005055c7ad784a9ce" + integrity sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg== + +"@types/node@*": + version "20.8.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.4.tgz#0e9ebb2ff29d5c3302fc84477d066fa7c6b441aa" + integrity sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A== + dependencies: + undici-types "~5.25.1" + +"@types/node@^17.0.5": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/parse5@^5.0.0": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" + integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== + +"@types/prop-types@*": + version "15.7.8" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.8.tgz#805eae6e8f41bd19e88917d2ea200dc992f405d3" + integrity sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ== + +"@types/qs@*": + version "6.9.8" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.8.tgz#f2a7de3c107b89b441e071d5472e6b726b4adf45" + integrity sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg== + +"@types/range-parser@*": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.5.tgz#38bd1733ae299620771bd414837ade2e57757498" + integrity sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA== + +"@types/react-router-config@*", "@types/react-router-config@^5.0.6": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.8.tgz#dd00654de4d79927570a4a8807c4a728feed59f3" + integrity sha512-zBzYZsr05V9xRG96oQ/xBXHy5+fDCX5wL7bboM0FFoOYQp9Gxmz8uvuKSkLesNWHlICl+W1l64F7fmp/KsOkuw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "^5.1.0" + +"@types/react-router-dom@*": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*", "@types/react-router@^5.1.0": + version "5.1.20" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c" + integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + +"@types/react@*": + version "18.2.27" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.27.tgz#746e52b06f3ccd5d7a724fd53769b70792601440" + integrity sha512-Wfv7B7FZiR2r3MIqbAlXoY1+tXm4bOqfz4oRr+nyXdBqapDBZ0l/IGcSlAfvxIHEEJjkPU0MYAc/BlFPOcrgLw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/sax@^1.2.1": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.5.tgz#4392799e1770d24b6dc8d0c66c8882f8e1c38b3d" + integrity sha512-9jWta97bBVC027/MShr3gLab8gPhKy4l6qpb+UJLF5pDm3501NvA7uvqVCW+REFtx00oTi6Cq9JzLwgq6evVgw== + dependencies: + "@types/node" "*" + +"@types/scheduler@*": + version "0.16.4" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.4.tgz#fedc3e5b15c26dc18faae96bf1317487cb3658cf" + integrity sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ== + +"@types/send@*": + version "0.17.2" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.2.tgz#af78a4495e3c2b79bfbdac3955fdd50e03cc98f2" + integrity sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-index@^1.9.1": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.2.tgz#cb26e775678a8526b73a5d980a147518740aaecd" + integrity sha512-asaEIoc6J+DbBKXtO7p2shWUpKacZOoMBEGBgPG91P8xhO53ohzHWGCs4ScZo5pQMf5ukQzVT9fhX1WzpHihig== + dependencies: + "@types/express" "*" + +"@types/serve-static@*", "@types/serve-static@^1.13.10": + version "1.15.3" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.3.tgz#2cfacfd1fd4520bbc3e292cca432d5e8e2e3ee61" + integrity sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg== + dependencies: + "@types/http-errors" "*" + "@types/mime" "*" + "@types/node" "*" + +"@types/sockjs@^0.3.33": + version "0.3.34" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.34.tgz#43e10e549b36d2ba2589278f00f81b5d7ccda167" + integrity sha512-R+n7qBFnm/6jinlteC9DBL5dGiDGjWAvjo4viUanpnc/dG1y7uDoacXPIQ/PQEg1fI912SMHIa014ZjRpvDw4g== + dependencies: + "@types/node" "*" + +"@types/unist@^2", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.8.tgz#bb197b9639aa1a04cf464a617fe800cccd92ad5c" + integrity sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw== + +"@types/ws@^8.5.5": + version "8.5.6" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.6.tgz#e9ad51f0ab79b9110c50916c9fcbddc36d373065" + integrity sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.1" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.1.tgz#07773d7160494d56aa882d7531aac7319ea67c3b" + integrity sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ== + +"@types/yargs@^17.0.8": + version "17.0.28" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.28.tgz#d106e4301fbacde3d1796ab27374dd16588ec851" + integrity sha512-N3e3fkS86hNhtk6BEnc0rj3zcehaxx8QWhCROJkqpl5Zaoi7nAic3jH8q94jVD3zu5LGk+PUB6KAiDmimYOEQw== + dependencies: + "@types/yargs-parser" "*" + +"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" + integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" + integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" + integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" + integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-opt" "1.11.6" + "@webassemblyjs/wasm-parser" "1.11.6" + "@webassemblyjs/wast-printer" "1.11.6" + +"@webassemblyjs/wasm-gen@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" + integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" + integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-parser" "1.11.6" + +"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" + integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" + integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== + +acorn-walk@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.0.4, acorn@^8.7.1, acorn@^8.8.2: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + +address@^1.0.1, address@^1.1.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" + integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0, ajv@^8.9.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +algoliasearch-helper@^3.10.0: + version "3.14.2" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.14.2.tgz#c34cfe6cefcfecd65c60bcb8bf9b68134472d28c" + integrity sha512-FjDSrjvQvJT/SKMW74nPgFpsoPUwZCzGbCqbp8HhBFfSk/OvNFxzCaCmuO0p7AWeLy1gD+muFwQEkBwcl5H4pg== + dependencies: + "@algolia/events" "^4.0.1" + +algoliasearch@^4.13.1, algoliasearch@^4.19.1: + version "4.20.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.20.0.tgz#700c2cb66e14f8a288460036c7b2a554d0d93cf4" + integrity sha512-y+UHEjnOItoNy0bYO+WWmLWBlPwDjKHW6mNHrPi0NkuhpQOOEbrkwQH/wgKFDLh7qlKjzoKeiRtlpewDPDG23g== + dependencies: + "@algolia/cache-browser-local-storage" "4.20.0" + "@algolia/cache-common" "4.20.0" + "@algolia/cache-in-memory" "4.20.0" + "@algolia/client-account" "4.20.0" + "@algolia/client-analytics" "4.20.0" + "@algolia/client-common" "4.20.0" + "@algolia/client-personalization" "4.20.0" + "@algolia/client-search" "4.20.0" + "@algolia/logger-common" "4.20.0" + "@algolia/logger-console" "4.20.0" + "@algolia/requester-browser-xhr" "4.20.0" + "@algolia/requester-common" "4.20.0" + "@algolia/requester-node-http" "4.20.0" + "@algolia/transporter" "4.20.0" + +ansi-align@^3.0.0, ansi-align@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-flatten@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +autoprefixer@^10.4.12, autoprefixer@^10.4.7: + version "10.4.16" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8" + integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ== + dependencies: + browserslist "^4.21.10" + caniuse-lite "^1.0.30001538" + fraction.js "^4.3.6" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +axios@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" + integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== + dependencies: + follow-redirects "^1.14.7" + +babel-loader@^8.2.5: + version "8.3.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" + integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== + dependencies: + find-cache-dir "^3.3.1" + loader-utils "^2.0.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" + +babel-plugin-apply-mdx-type-prop@1.6.22: + version "1.6.22" + resolved "https://registry.yarnpkg.com/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz#d216e8fd0de91de3f1478ef3231e05446bc8705b" + integrity sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ== + dependencies: + "@babel/helper-plugin-utils" "7.10.4" + "@mdx-js/util" "1.6.22" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-extract-import-names@1.6.22: + version "1.6.22" + resolved "https://registry.yarnpkg.com/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz#de5f9a28eb12f3eb2578bf74472204e66d1a13dc" + integrity sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ== + dependencies: + "@babel/helper-plugin-utils" "7.10.4" + +babel-plugin-polyfill-corejs2@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c" + integrity sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.4.2" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.8.3: + version "0.8.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.4.tgz#1fac2b1dcef6274e72b3c72977ed8325cb330591" + integrity sha512-9l//BZZsPR+5XjyJMPtZSK4jv0BsTO1zDac2GC6ygx9WLGlcsnRd1Co0B2zT5fF5Ic6BZy+9m3HNZ3QcOeDKfg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.4.2" + core-js-compat "^3.32.2" + +babel-plugin-polyfill-regenerator@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz#80d0f3e1098c080c8b5a65f41e9427af692dc326" + integrity sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.4.2" + +bail@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" + integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base16@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" + integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ== + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +bonjour-service@^1.0.11: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.1.tgz#960948fa0e0153f5d26743ab15baf8e33752c135" + integrity sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg== + dependencies: + array-flatten "^2.1.2" + dns-equal "^1.0.0" + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +boxen@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + +boxen@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d" + integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw== + dependencies: + ansi-align "^3.0.1" + camelcase "^6.2.0" + chalk "^4.1.2" + cli-boxes "^3.0.0" + string-width "^5.0.1" + type-fest "^2.5.0" + widest-line "^4.0.1" + wrap-ansi "^8.0.1" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.21.9, browserslist@^4.22.1: + version "4.22.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" + integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== + dependencies: + caniuse-lite "^1.0.30001541" + electron-to-chromium "^1.4.535" + node-releases "^2.0.13" + update-browserslist-db "^1.0.13" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase-css@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541: + version "1.0.30001547" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz#d4f92efc488aab3c7f92c738d3977c2a3180472b" + integrity sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA== + +ccount@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" + integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +character-entities-legacy@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== + +character-entities@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== + +character-reference-invalid@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== + +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@^1.0.0-rc.12: + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" + integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.0.1" + htmlparser2 "^8.0.1" + parse5 "^7.0.0" + parse5-htmlparser2-tree-adapter "^7.0.0" + +chokidar@^3.4.2, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +clean-css@^5.2.2, clean-css@^5.3.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.2.tgz#70ecc7d4d4114921f5d298349ff86a31a9975224" + integrity sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww== + dependencies: + source-map "~0.6.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== + +cli-table3@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" + integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + +clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + +collapse-white-space@^1.0.2: + version "1.0.6" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" + integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colord@^2.9.1: + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + +colorette@^2.0.10: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +combine-promises@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.2.0.tgz#5f2e68451862acf85761ded4d9e2af7769c2ca6a" + integrity sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ== + +comma-separated-tokens@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +consola@^2.15.3: + version "2.15.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" + integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +copy-text-to-clipboard@^3.0.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz#0202b2d9bdae30a49a53f898626dcc3b49ad960b" + integrity sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q== + +copy-webpack-plugin@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== + dependencies: + fast-glob "^3.2.11" + glob-parent "^6.0.1" + globby "^13.1.1" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + +core-js-compat@^3.31.0, core-js-compat@^3.32.2: + version "3.33.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.33.0.tgz#24aa230b228406450b2277b7c8bfebae932df966" + integrity sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw== + dependencies: + browserslist "^4.22.1" + +core-js-pure@^3.30.2: + version "3.33.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.33.0.tgz#938a28754b4d82017a7a8cbd2727b1abecc63591" + integrity sha512-FKSIDtJnds/YFIEaZ4HszRX7hkxGpNKM7FC9aJ9WLJbSd3lD4vOltFuVIBLR8asSx9frkTSqL0dw90SKQxgKrg== + +core-js@^3.23.3: + version "3.33.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.33.0.tgz#70366dbf737134761edb017990cf5ce6c6369c40" + integrity sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + +cosmiconfig@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cosmiconfig@^8.2.0: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +cross-fetch@^3.1.5: + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== + dependencies: + node-fetch "^2.6.12" + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +css-declaration-sorter@^6.3.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" + integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== + +css-loader@^6.7.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.8.1.tgz#0f8f52699f60f5e679eab4ec0fcd68b8e8a50a88" + integrity sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.21" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.3" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.3.8" + +css-minimizer-webpack-plugin@^4.0.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz#79f6199eb5adf1ff7ba57f105e3752d15211eb35" + integrity sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA== + dependencies: + cssnano "^5.1.8" + jest-worker "^29.1.2" + postcss "^8.4.17" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + +css-select@^4.1.3: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.0.1, css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-advanced@^5.3.8: + version "5.3.10" + resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.10.tgz#25558a1fbf3a871fb6429ce71e41be7f5aca6eef" + integrity sha512-fnYJyCS9jgMU+cmHO1rPSPf9axbQyD7iUhLO5Df6O4G+fKIOMps+ZbU0PdGFejFBBZ3Pftf18fn1eG7MAPUSWQ== + dependencies: + autoprefixer "^10.4.12" + cssnano-preset-default "^5.2.14" + postcss-discard-unused "^5.1.0" + postcss-merge-idents "^5.1.1" + postcss-reduce-idents "^5.2.0" + postcss-zindex "^5.1.0" + +cssnano-preset-default@^5.2.14: + version "5.2.14" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8" + integrity sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A== + dependencies: + css-declaration-sorter "^6.3.1" + cssnano-utils "^3.1.0" + postcss-calc "^8.2.3" + postcss-colormin "^5.3.1" + postcss-convert-values "^5.1.3" + postcss-discard-comments "^5.1.2" + postcss-discard-duplicates "^5.1.0" + postcss-discard-empty "^5.1.1" + postcss-discard-overridden "^5.1.0" + postcss-merge-longhand "^5.1.7" + postcss-merge-rules "^5.1.4" + postcss-minify-font-values "^5.1.0" + postcss-minify-gradients "^5.1.1" + postcss-minify-params "^5.1.4" + postcss-minify-selectors "^5.2.1" + postcss-normalize-charset "^5.1.0" + postcss-normalize-display-values "^5.1.0" + postcss-normalize-positions "^5.1.1" + postcss-normalize-repeat-style "^5.1.1" + postcss-normalize-string "^5.1.0" + postcss-normalize-timing-functions "^5.1.0" + postcss-normalize-unicode "^5.1.1" + postcss-normalize-url "^5.1.0" + postcss-normalize-whitespace "^5.1.1" + postcss-ordered-values "^5.1.3" + postcss-reduce-initial "^5.1.2" + postcss-reduce-transforms "^5.1.0" + postcss-svgo "^5.1.0" + postcss-unique-selectors "^5.1.1" + +cssnano-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" + integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== + +cssnano@^5.1.12, cssnano@^5.1.8: + version "5.1.15" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf" + integrity sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw== + dependencies: + cssnano-preset-default "^5.2.14" + lilconfig "^2.0.3" + yaml "^1.10.2" + +csso@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + +csstype@^3.0.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + +debug@2.6.9, debug@^2.6.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== + dependencies: + mimic-response "^1.0.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + +define-data-property@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" + integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +del@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" + integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detab@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43" + integrity sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g== + dependencies: + repeat-string "^1.5.4" + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +detect-port-alt@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" + integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== + dependencies: + address "^1.0.1" + debug "^2.6.0" + +detect-port@^1.3.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" + integrity sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ== + dependencies: + address "^1.0.1" + debug "4" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== + +dns-packet@^5.2.2: + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +dom-converter@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^2.5.2, domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +duplexer3@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" + integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== + +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.535: + version "1.4.548" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.548.tgz#e695d769e0e801fa6d438b63f6bc9b80372000d6" + integrity sha512-R77KD6mXv37DOyKLN/eW1rGS61N6yHOfapNSX9w+y9DdPG83l9Gkuv7qkCFZ4Ta4JPhrjgQfYbv4Y3TnM1Hi2Q== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +emoticon@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-3.2.0.tgz#c008ca7d7620fac742fe1bf4af8ff8fed154ae7f" + integrity sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enhanced-resolve@^5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-module-lexer@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1" + integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + +escape-html@^1.0.3, escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eta@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eta/-/eta-2.2.0.tgz#eb8b5f8c4e8b6306561a455e62cd7492fe3a9b8a" + integrity sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eval@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.8.tgz#2b903473b8cc1d1989b83a1e7923f883eb357f85" + integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw== + dependencies: + "@types/node" "*" + require-like ">= 0.1.1" + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +express@^4.17.3: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-url-parser@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" + integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== + dependencies: + punycode "^1.3.2" + +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +fbemitter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3" + integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw== + dependencies: + fbjs "^3.0.0" + +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@^3.0.0, fbjs@^3.0.1: + version "3.0.5" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.5.tgz#aa0edb7d5caa6340011790bd9249dbef8a81128d" + integrity sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg== + dependencies: + cross-fetch "^3.1.5" + fbjs-css-vars "^1.0.0" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^1.0.35" + +feed@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e" + integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ== + dependencies: + xml-js "^1.6.11" + +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +filesize@^8.0.6: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-cache-dir@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flux@^4.0.1: + version "4.0.4" + resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.4.tgz#9661182ea81d161ee1a6a6af10d20485ef2ac572" + integrity sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw== + dependencies: + fbemitter "^3.0.0" + fbjs "^3.0.1" + +follow-redirects@^1.0.0, follow-redirects@^1.14.7: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + +fork-ts-checker-webpack-plugin@^6.5.0: + version "6.5.3" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz#eda2eff6e22476a2688d10661688c47f611b37f3" + integrity sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ== + dependencies: + "@babel/code-frame" "^7.8.3" + "@types/json-schema" "^7.0.5" + chalk "^4.1.0" + chokidar "^3.4.2" + cosmiconfig "^6.0.0" + deepmerge "^4.2.2" + fs-extra "^9.0.0" + glob "^7.1.6" + memfs "^3.1.2" + minimatch "^3.0.4" + schema-utils "2.7.0" + semver "^7.3.2" + tapable "^1.0.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fraction.js@^4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d" + integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-monkey@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788" + integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +github-slugger@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" + integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-dirs@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" + integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== + dependencies: + ini "2.0.0" + +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +globby@^13.1.1: + version "13.2.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" + integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.3.0" + ignore "^5.2.4" + merge2 "^1.4.1" + slash "^4.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +gray-matter@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" + integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== + dependencies: + js-yaml "^3.13.1" + kind-of "^6.0.2" + section-matter "^1.0.0" + strip-bom-string "^1.0.0" + +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + +has@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" + integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== + +hast-to-hyperscript@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz#9b67fd188e4c81e8ad66f803855334173920218d" + integrity sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA== + dependencies: + "@types/unist" "^2.0.3" + comma-separated-tokens "^1.0.0" + property-information "^5.3.0" + space-separated-tokens "^1.0.0" + style-to-object "^0.3.0" + unist-util-is "^4.0.0" + web-namespaces "^1.0.0" + +hast-util-from-parse5@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a" + integrity sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA== + dependencies: + "@types/parse5" "^5.0.0" + hastscript "^6.0.0" + property-information "^5.0.0" + vfile "^4.0.0" + vfile-location "^3.2.0" + web-namespaces "^1.0.0" + +hast-util-parse-selector@^2.0.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" + integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== + +hast-util-raw@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.0.1.tgz#973b15930b7529a7b66984c98148b46526885977" + integrity sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig== + dependencies: + "@types/hast" "^2.0.0" + hast-util-from-parse5 "^6.0.0" + hast-util-to-parse5 "^6.0.0" + html-void-elements "^1.0.0" + parse5 "^6.0.0" + unist-util-position "^3.0.0" + vfile "^4.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + +hast-util-to-parse5@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" + integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ== + dependencies: + hast-to-hyperscript "^9.0.0" + property-information "^5.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + +hastscript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" + integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== + dependencies: + "@types/hast" "^2.0.0" + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hoist-non-react-statics@^3.1.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-entities@^2.3.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" + integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== + +html-minifier-terser@^6.0.2, html-minifier-terser@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" + integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== + dependencies: + camel-case "^4.1.2" + clean-css "^5.2.2" + commander "^8.3.0" + he "^1.2.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.10.0" + +html-tags@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== + +html-void-elements@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" + integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== + +html-webpack-plugin@^5.5.0: + version "5.5.3" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz#72270f4a78e222b5825b296e5e3e1328ad525a3e" + integrity sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg== + dependencies: + "@types/html-minifier-terser" "^6.0.0" + html-minifier-terser "^6.0.2" + lodash "^4.17.21" + pretty-error "^4.0.0" + tapable "^2.0.0" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +htmlparser2@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + +http-cache-semantics@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + +http-proxy-middleware@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" + integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +ignore@^5.2.0, ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +image-size@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.2.tgz#d778b6d0ab75b2737c1556dd631652eb963bc486" + integrity sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg== + dependencies: + queue "6.0.2" + +immer@^9.0.7: + version "9.0.21" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" + integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== + +import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infima@0.2.0-alpha.43: + version "0.2.0-alpha.43" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.43.tgz#f7aa1d7b30b6c08afef441c726bac6150228cbe0" + integrity sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ini@^1.3.5, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +inline-style-parser@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" + integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" + integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== + +is-alphabetical@1.0.4, is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extendable@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-hexadecimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== + +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== + +is-root@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" + integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-whitespace-character@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" + integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== + +is-word-character@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" + integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^29.1.2: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jiti@^1.18.2: + version "1.20.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.20.0.tgz#2d823b5852ee8963585c8dd8b7992ffc1ae83b42" + integrity sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA== + +joi@^17.6.0: + version "17.11.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.11.0.tgz#aa9da753578ec7720e6f0ca2c7046996ed04fc1a" + integrity sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.3" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json5@^2.1.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +latest-version@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + +launch-editor@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.1.tgz#f259c9ef95cbc9425620bbbd14b468fcdb4ffe3c" + integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== + dependencies: + picocolors "^1.0.0" + shell-quote "^1.8.1" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lilconfig@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +loader-utils@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +loader-utils@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576" + integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.curry@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" + integrity sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA== + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.escape@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" + integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + +lodash.flow@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" + integrity sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw== + +lodash.invokemap@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz#1748cda5d8b0ef8369c4eb3ec54c21feba1f2d62" + integrity sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w== + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.pullall@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.pullall/-/lodash.pullall-4.2.0.tgz#9d98b8518b7c965b0fae4099bd9fb7df8bbf38ba" + integrity sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg== + +lodash.uniq@4.5.0, lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + +lodash.uniqby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" + integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== + +lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +markdown-escapes@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" + integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== + +mdast-squeeze-paragraphs@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97" + integrity sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ== + dependencies: + unist-util-remove "^2.0.0" + +mdast-util-definitions@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" + integrity sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ== + dependencies: + unist-util-visit "^2.0.0" + +mdast-util-to-hast@10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb" + integrity sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + mdast-util-definitions "^4.0.0" + mdurl "^1.0.0" + unist-builder "^2.0.0" + unist-util-generated "^1.0.0" + unist-util-position "^3.0.0" + unist-util-visit "^2.0.0" + +mdast-util-to-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b" + integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w== + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +mdurl@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^3.1.2, memfs@^3.4.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== + dependencies: + fs-monkey "^1.0.4" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== + +mime-types@2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" + +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mini-css-extract-plugin@^2.6.1: + version "2.7.6" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz#282a3d38863fddcd2e0c220aaed5b90bc156564d" + integrity sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw== + dependencies: + schema-utils "^4.0.0" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.5: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mrmime@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" + integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-emoji@^1.10.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== + dependencies: + lodash "^4.17.21" + +node-fetch@^2.6.12: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-forge@^1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-releases@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +normalize-url@^4.1.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== + +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nprogress@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" + integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA== + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^8.0.9, open@^8.4.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + +param-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + +parse-json@^5.0.0, parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-numeric-range@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" + integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== + +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" + integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + dependencies: + domhandler "^5.0.2" + parse5 "^7.0.0" + +parse5@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +parse5@^7.0.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-is-inside@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-to-regexp@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" + integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +postcss-calc@^8.2.3: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== + dependencies: + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + +postcss-colormin@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f" + integrity sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" + integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA== + dependencies: + browserslist "^4.21.4" + postcss-value-parser "^4.2.0" + +postcss-discard-comments@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" + integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== + +postcss-discard-duplicates@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" + integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== + +postcss-discard-empty@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" + integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== + +postcss-discard-overridden@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" + integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== + +postcss-discard-unused@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz#8974e9b143d887677304e558c1166d3762501142" + integrity sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-loader@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.3.tgz#6da03e71a918ef49df1bb4be4c80401df8e249dd" + integrity sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA== + dependencies: + cosmiconfig "^8.2.0" + jiti "^1.18.2" + semver "^7.3.8" + +postcss-merge-idents@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz#7753817c2e0b75d0853b56f78a89771e15ca04a1" + integrity sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw== + dependencies: + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-merge-longhand@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" + integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^5.1.1" + +postcss-merge-rules@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c" + integrity sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + cssnano-utils "^3.1.0" + postcss-selector-parser "^6.0.5" + +postcss-minify-font-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" + integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" + integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== + dependencies: + colord "^2.9.1" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" + integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw== + dependencies: + browserslist "^4.21.4" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" + integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" + integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-normalize-charset@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" + integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== + +postcss-normalize-display-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" + integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" + integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" + integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" + integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" + integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" + integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA== + dependencies: + browserslist "^4.21.4" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" + integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== + dependencies: + normalize-url "^6.0.1" + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" + integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-ordered-values@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" + integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== + dependencies: + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-reduce-idents@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz#c89c11336c432ac4b28792f24778859a67dfba95" + integrity sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-reduce-initial@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" + integrity sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" + integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: + version "6.0.13" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" + integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-sort-media-queries@^4.2.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.4.1.tgz#04a5a78db3921eb78f28a1a781a2e68e65258128" + integrity sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw== + dependencies: + sort-css-media-queries "2.1.0" + +postcss-svgo@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" + integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^2.7.0" + +postcss-unique-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" + integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss-zindex@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.1.0.tgz#4a5c7e5ff1050bd4c01d95b1847dfdcc58a496ff" + integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== + +postcss@^8.3.11, postcss@^8.4.14, postcss@^8.4.17, postcss@^8.4.21: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== + +pretty-error@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" + integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== + dependencies: + lodash "^4.17.20" + renderkid "^3.0.0" + +pretty-time@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" + integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== + +prism-react-renderer@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz#786bb69aa6f73c32ba1ee813fbe17a0115435085" + integrity sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg== + +prismjs@^1.28.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + +prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +prop-types@^15.6.2, prop-types@^15.7.2: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +property-information@^5.0.0, property-information@^5.3.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" + integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== + dependencies: + xtend "^4.0.0" + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +pupa@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + +pure-color@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" + integrity sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +rc@1.2.8, rc@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-base16-styling@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" + integrity sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ== + dependencies: + base16 "^1.0.0" + lodash.curry "^4.0.1" + lodash.flow "^3.3.0" + pure-color "^1.2.0" + +react-dev-utils@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" + integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ== + dependencies: + "@babel/code-frame" "^7.16.0" + address "^1.1.2" + browserslist "^4.18.1" + chalk "^4.1.2" + cross-spawn "^7.0.3" + detect-port-alt "^1.1.6" + escape-string-regexp "^4.0.0" + filesize "^8.0.6" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.5.0" + global-modules "^2.0.0" + globby "^11.0.4" + gzip-size "^6.0.0" + immer "^9.0.7" + is-root "^2.1.0" + loader-utils "^3.2.0" + open "^8.4.0" + pkg-up "^3.1.0" + prompts "^2.4.2" + react-error-overlay "^6.0.11" + recursive-readdir "^2.2.2" + shell-quote "^1.7.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.2" + +react-error-overlay@^6.0.11: + version "6.0.11" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" + integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== + +react-fast-compare@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + +react-helmet-async@*, react-helmet-async@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e" + integrity sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg== + dependencies: + "@babel/runtime" "^7.12.5" + invariant "^2.2.4" + prop-types "^15.7.2" + react-fast-compare "^3.2.0" + shallowequal "^1.1.0" + +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-json-view@^1.21.3: + version "1.21.3" + resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475" + integrity sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw== + dependencies: + flux "^4.0.1" + react-base16-styling "^0.6.0" + react-lifecycles-compat "^3.0.4" + react-textarea-autosize "^8.3.2" + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-loadable-ssr-addon-v5-slorber@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883" + integrity sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A== + dependencies: + "@babel/runtime" "^7.10.3" + +react-router-config@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" + integrity sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg== + dependencies: + "@babel/runtime" "^7.1.2" + +react-router-dom@^5.3.3: + version "5.3.4" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6" + integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ== + dependencies: + "@babel/runtime" "^7.12.13" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.3.4" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.3.4, react-router@^5.3.3: + version "5.3.4" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5" + integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== + dependencies: + "@babel/runtime" "^7.12.13" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-textarea-autosize@^8.3.2: + version "8.5.3" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409" + integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ== + dependencies: + "@babel/runtime" "^7.20.13" + use-composed-ref "^1.3.0" + use-latest "^1.2.1" + +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +readable-stream@^2.0.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reading-time@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb" + integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +recursive-readdir@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz#e726f328c0d69153bcabd5c322d3195252379372" + integrity sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA== + dependencies: + minimatch "^3.0.5" + +regenerate-unicode-properties@^10.1.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" + integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== + dependencies: + "@babel/regjsgen" "^0.8.0" + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +registry-auth-token@^4.0.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.2.tgz#f02d49c3668884612ca031419491a13539e21fac" + integrity sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg== + dependencies: + rc "1.2.8" + +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== + dependencies: + jsesc "~0.5.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== + +remark-emoji@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.2.0.tgz#1c702090a1525da5b80e15a8f963ef2c8236cac7" + integrity sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w== + dependencies: + emoticon "^3.2.0" + node-emoji "^1.10.0" + unist-util-visit "^2.0.3" + +remark-footnotes@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f" + integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ== + +remark-mdx@1.6.22: + version "1.6.22" + resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" + integrity sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ== + dependencies: + "@babel/core" "7.12.9" + "@babel/helper-plugin-utils" "7.10.4" + "@babel/plugin-proposal-object-rest-spread" "7.12.1" + "@babel/plugin-syntax-jsx" "7.12.1" + "@mdx-js/util" "1.6.22" + is-alphabetical "1.0.4" + remark-parse "8.0.3" + unified "9.2.0" + +remark-parse@8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" + integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== + dependencies: + ccount "^1.0.0" + collapse-white-space "^1.0.2" + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^2.0.0" + vfile-location "^3.0.0" + xtend "^4.0.1" + +remark-squeeze-paragraphs@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz#76eb0e085295131c84748c8e43810159c5653ead" + integrity sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw== + dependencies: + mdast-squeeze-paragraphs "^4.0.0" + +renderkid@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" + integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^6.0.1" + +repeat-string@^1.5.4: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +"require-like@>= 0.1.1": + version "0.1.2" + resolved "https://registry.yarnpkg.com/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa" + integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve@^1.1.6, resolve@^1.14.2, resolve@^1.3.2: + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== + dependencies: + lowercase-keys "^1.0.0" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rtl-detect@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6" + integrity sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ== + +rtlcss@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3" + integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A== + dependencies: + find-up "^5.0.0" + picocolors "^1.0.0" + postcss "^8.3.11" + strip-json-comments "^3.1.1" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^7.5.4: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" + integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== + +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +schema-utils@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== + dependencies: + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" + +schema-utils@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" + integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +section-matter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" + integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== + dependencies: + extend-shallow "^2.0.1" + kind-of "^6.0.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== + +selfsigned@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" + integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== + dependencies: + node-forge "^1" + +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + +semver@^5.4.1: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.3.8: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" + integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== + dependencies: + randombytes "^2.1.0" + +serve-handler@^6.1.3: + version "6.1.5" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375" + integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg== + dependencies: + bytes "3.0.0" + content-disposition "0.5.2" + fast-url-parser "1.1.3" + mime-types "2.1.18" + minimatch "3.1.2" + path-is-inside "1.0.2" + path-to-regexp "2.2.1" + range-parser "1.2.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.7.3, shell-quote@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sirv@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446" + integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA== + dependencies: + "@polka/url" "^1.0.0-next.20" + mrmime "^1.0.0" + totalist "^3.0.0" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +sitemap@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef" + integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== + dependencies: + "@types/node" "^17.0.5" + "@types/sax" "^1.2.1" + arg "^5.0.0" + sax "^1.2.4" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +sort-css-media-queries@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz#7c85e06f79826baabb232f5560e9745d7a78c4ce" + integrity sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA== + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +space-separated-tokens@^1.0.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" + integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +state-toggle@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" + integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +std-env@^3.0.1: + version "3.4.3" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.4.3.tgz#326f11db518db751c83fd58574f449b7c3060910" + integrity sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q== + +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" + integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +style-to-object@0.3.0, style-to-object@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" + integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== + dependencies: + inline-style-parser "0.1.1" + +stylehacks@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" + integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw== + dependencies: + browserslist "^4.21.4" + postcss-selector-parser "^6.0.4" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svg-parser@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + +svgo@^2.7.0, svgo@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + +tapable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.3.3, terser-webpack-plugin@^5.3.7: + version "5.3.9" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" + integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.17" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.16.8" + +terser@^5.10.0, terser@^5.16.8: + version "5.21.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.21.0.tgz#d2b27e92b5e56650bc83b6defa00a110f0b124b2" + integrity sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +tiny-invariant@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" + integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== + +tiny-warning@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +totalist@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" + integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +trim-trailing-lines@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" + integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ== + +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + integrity sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ== + +trough@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" + integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== + +tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^2.5.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^4.7.4: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +ua-parser-js@^1.0.35: + version "1.0.36" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.36.tgz#a9ab6b9bd3a8efb90bb0816674b412717b7c428c" + integrity sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw== + +undici-types@~5.25.1: + version "5.25.3" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" + integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== + +unherit@^1.0.4: + version "1.1.3" + resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" + integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== + dependencies: + inherits "^2.0.0" + xtend "^4.0.0" + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +unified@9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" + integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + +unified@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" + integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +unist-builder@2.0.3, unist-builder@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" + integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== + +unist-util-generated@^1.0.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" + integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== + +unist-util-is@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" + integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== + +unist-util-position@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" + integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== + +unist-util-remove-position@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" + integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== + dependencies: + unist-util-visit "^2.0.0" + +unist-util-remove@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.1.0.tgz#b0b4738aa7ee445c402fda9328d604a02d010588" + integrity sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q== + dependencies: + unist-util-is "^4.0.0" + +unist-util-stringify-position@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== + dependencies: + "@types/unist" "^2.0.2" + +unist-util-visit-parents@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" + integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + +unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +update-notifier@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== + dependencies: + boxen "^5.0.0" + chalk "^4.1.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url-loader@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" + integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== + dependencies: + loader-utils "^2.0.0" + mime-types "^2.1.27" + schema-utils "^3.0.0" + +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== + dependencies: + prepend-http "^2.0.0" + +use-composed-ref@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" + integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== + +use-isomorphic-layout-effect@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" + integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== + +use-latest@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" + integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== + dependencies: + use-isomorphic-layout-effect "^1.1.1" + +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== + +utility-types@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vfile-location@^3.0.0, vfile-location@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" + integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== + +vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + +vfile@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" + integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + +wait-on@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e" + integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw== + dependencies: + axios "^0.25.0" + joi "^17.6.0" + lodash "^4.17.21" + minimist "^1.2.5" + rxjs "^7.5.4" + +watchpack@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +web-namespaces@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" + integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +webpack-bundle-analyzer@^4.5.0: + version "4.9.1" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz#d00bbf3f17500c10985084f22f1a2bf45cb2f09d" + integrity sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w== + dependencies: + "@discoveryjs/json-ext" "0.5.7" + acorn "^8.0.4" + acorn-walk "^8.0.0" + commander "^7.2.0" + escape-string-regexp "^4.0.0" + gzip-size "^6.0.0" + is-plain-object "^5.0.0" + lodash.debounce "^4.0.8" + lodash.escape "^4.0.1" + lodash.flatten "^4.4.0" + lodash.invokemap "^4.6.0" + lodash.pullall "^4.2.0" + lodash.uniqby "^4.7.0" + opener "^1.5.2" + picocolors "^1.0.0" + sirv "^2.0.3" + ws "^7.3.1" + +webpack-dev-middleware@^5.3.1: + version "5.3.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" + integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== + dependencies: + colorette "^2.0.10" + memfs "^3.4.3" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@^4.9.3: + version "4.15.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz#8944b29c12760b3a45bdaa70799b17cb91b03df7" + integrity sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.5" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + launch-editor "^2.6.0" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^5.3.1" + ws "^8.13.0" + +webpack-merge@^5.8.0: + version "5.9.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826" + integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" + +webpack-sources@^3.2.2, webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.73.0: + version "5.88.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.2.tgz#f62b4b842f1c6ff580f3fcb2ed4f0b579f4c210e" + integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^1.0.0" + "@webassemblyjs/ast" "^1.11.5" + "@webassemblyjs/wasm-edit" "^1.11.5" + "@webassemblyjs/wasm-parser" "^1.11.5" + acorn "^8.7.1" + acorn-import-assertions "^1.9.0" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.15.0" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.7" + watchpack "^2.4.0" + webpack-sources "^3.2.3" + +webpackbar@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570" + integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ== + dependencies: + chalk "^4.1.0" + consola "^2.15.3" + pretty-time "^1.1.0" + std-env "^3.0.1" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + +widest-line@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" + integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== + dependencies: + string-width "^5.0.1" + +wildcard@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^7.3.1: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +ws@^8.13.0: + version "8.14.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" + integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== + +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + +xml-js@^1.6.11: + version "1.6.11" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" + integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== + dependencies: + sax "^1.2.4" + +xtend@^4.0.0, xtend@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zwitch@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" + integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== diff --git a/misc/gendocs/Makefile b/misc/gendocs/Makefile index 8a4f3ba2de5..0f56d83693f 100644 --- a/misc/gendocs/Makefile +++ b/misc/gendocs/Makefile @@ -1,5 +1,8 @@ all: clean gen +install: + go install golang.org/x/pkgsite/cmd/pkgsite@latest + gen: ./gendocs.sh @@ -7,4 +10,4 @@ clean: rm -rf godoc kill_zombies: - kill -9 `lsof -t -i tcp:6060 -s TCP:LISTEN` || true + kill -9 `lsof -t -i tcp:8080 -s TCP:LISTEN` || true diff --git a/misc/gendocs/gendocs.sh b/misc/gendocs/gendocs.sh index d7621acd5a0..b50c597bc39 100755 --- a/misc/gendocs/gendocs.sh +++ b/misc/gendocs/gendocs.sh @@ -1,35 +1,58 @@ -#!/bin/sh - -GODOC_PORT=${GODOC_PORT:-6060} -GO_MODULE=${GO_MODULE:-github.com/gnolang/gno} -GODOC_OUT=${GODOC_OUT:-godoc} -URL=http://localhost:${GODOC_PORT}/pkg/github.com/gnolang/gno/ - -echo "[+] Starting godoc server..." -go run \ - -modfile ../devdeps/go.mod \ - golang.org/x/tools/cmd/godoc \ - -http="localhost:${GODOC_PORT}" & -PID=$! -# Waiting for godoc server -while ! curl --fail --silent "$URL" > /dev/null 2>&1; do - sleep 0.1 +#!/bin/bash +# Heavily modified version of the following script: +# https://gist.github.com/Kegsay/84ce060f237cb9ab4e0d2d321a91d920 +set -u + +DOC_DIR=godoc +PKG=github.com/gnolang/gno +# Used to load /static content +STATIC_PREFIX=/gno + +# Run a pkgsite server which we will scrape. Use env to run it from our repo's root directory. +env -C ../.. pkgsite & +DOC_PID=$! + +# Wait for the server to init +while : +do + curl -s "http://localhost:8080" > /dev/null + if [ $? -eq 0 ] # exit code is 0 if we connected + then + break + fi done -echo "[+] Downloading godoc pages..." +# Scrape the pkg directory for the API docs. Scrap lib for the CSS/JS. Ignore everything else. wget \ - --recursive \ - --no-verbose \ - --convert-links \ - --page-requisites \ - --adjust-extension \ - --execute=robots=off \ - --include-directories="/lib,/pkg/$GO_MODULE,/src/$GO_MODULE" \ - --exclude-directories="*" \ - --directory-prefix="${GODOC_OUT}" \ - --no-host-directories \ - "$URL?m=all" - -echo "[+] Killing godoc server..." -kill -9 "$PID" + --verbose \ + --recursive \ + --mirror \ + --convert-links \ + --adjust-extension \ + --page-requisites \ + -erobots=off \ + --accept-regex='8080/((search|license-policy|about|)$|(static|images)/|github.com/gnolang/)' \ + http://localhost:8080/ \ + http://localhost:8080/static/frontend/frontend.js \ + http://localhost:8080/static/frontend/unit/unit.js \ + http://localhost:8080/static/frontend/unit/main/main.js \ + http://localhost:8080/third_party/dialog-polyfill/dialog-polyfill.js + +# Stop the pkgsite server +kill -9 $DOC_PID + +# Delete the old directory or else mv will put the localhost dir into +# the DOC_DIR if it already exists. +rm -rf $DOC_DIR +mv localhost\:8080 $DOC_DIR + +# Perform various replacements to fix broken links/UI. +# /files/ will point to their github counterparts; we make links to importedby/version go nowhere; +# any other link will point to pkg.go.dev, and fix the /files/... text when viewing a pkg. +find godoc -type f -exec sed -ri 's#http://localhost:8080/files/[^"]*/github.com/gnolang/([^/"]+)/([^"]*)#https://github.com/gnolang/\1/blob/master/\2#g +s#http://localhost:8080/[^"?]*\?tab=(importedby|versions)#\##g +s#http://localhost:8080([^")]*)#https://pkg.go.dev\1#g +s#/files/[^" ]*/(github.com/[^" ]*)/#\1#g +s#s\.src = src;#s.src = "'"$STATIC_PREFIX"'" + src;#g' {} + +echo "Docs can be found in $DOC_DIR" diff --git a/misc/list-gnophers/main.sh b/misc/list-gnophers/main.sh new file mode 100755 index 00000000000..0b230e97948 --- /dev/null +++ b/misc/list-gnophers/main.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +main() { + cd "$(dirname "$0")" + cd ../.. + fname="$(mktemp --tmpdir gno_file_commits.XXXXXXXXXX.csv)" + for file in $(list_gno_files); do + extract_file_metadata $file + done > "$fname" + cat "$fname" | sort_by_date | unique_by_author +} + +list_gno_files() { + # list .gno file in examples/, remove tests and unit tests + find ./examples -name "*.gno" | grep -v _filetest.gno | grep -v _test.gno | grep -v gno.land/r/demo/tests +} + +extract_file_metadata() { + file=$1 + # get the first commit date of the file + first_commit_date=$(git log --pretty=format:%ct --follow $file | tail -n 1) + # get the email of the first contributor of the file + email=$(git log --mailmap --pretty=format:%aE --follow $file | tail -n 1) + # print the file name, first commit date, and email + echo "$first_commit_date,$email,$file" +} + +sort_by_date() { + sort -t, -k1 +} + +unique_by_author() { + awk -F, '!seek[$2]++' +} + +main diff --git a/misc/logos/buffer.go b/misc/logos/buffer.go index 90665f79bfd..81e8d1abc75 100644 --- a/misc/logos/buffer.go +++ b/misc/logos/buffer.go @@ -7,7 +7,7 @@ import ( "github.com/gdamore/tcell/v2" ) -//---------------------------------------- +// ---------------------------------------- // Buffer // A Buffer is a buffer area in which to draw. @@ -82,7 +82,7 @@ func (bb *Buffer) DrawToScreen(s tcell.Screen) { } } -//---------------------------------------- +// ---------------------------------------- // Cell // A terminal character cell. @@ -131,10 +131,6 @@ var gDefaultSpaceTStyle = tcell.StyleDefault. Dim(true). Background(tcell.ColorGray) -var gDefaultTStyle = gDefaultStyle.GetTStyle(). - Foreground(gDefaultForeground). - Background(gDefaultBackground) - // This is where a bit of dynamic logic is performed, // namely where the attr is used to derive the final style. func (cc *Cell) GetTCellContent() (mainc rune, combc []rune, tstyle tcell.Style) { @@ -161,7 +157,7 @@ func (cc *Cell) GetTCellContent() (mainc rune, combc []rune, tstyle tcell.Style) return } -//---------------------------------------- +// ---------------------------------------- // View // analogy: "Buffer:View :: array:slice". @@ -205,7 +201,7 @@ func (bs View) GetCell(x, y int) *Cell { ) } -//---------------------------------------- +// ---------------------------------------- // BufferedView // A view onto an element. diff --git a/misc/logos/cmd/logos.go b/misc/logos/cmd/logos.go index 228895f852d..3a374fecba2 100644 --- a/misc/logos/cmd/logos.go +++ b/misc/logos/cmd/logos.go @@ -10,11 +10,6 @@ import ( "github.com/gnolang/gno/misc/logos" ) -var ( - row = 0 - style = tcell.StyleDefault -) - func main() { encoding.Register() diff --git a/misc/logos/debug.go b/misc/logos/debug.go deleted file mode 100644 index 8b2a4692bf4..00000000000 --- a/misc/logos/debug.go +++ /dev/null @@ -1,26 +0,0 @@ -package logos - -import ( - "fmt" -) - -// NOTE: the golang compiler doesn't seem to be intelligent -// enough to remove steps when const debug is True, -// so it is still faster to first check the truth value -// before calling debug.Println or debug.Printf. - -const debug debugging = false // or flip - -type debugging bool - -func (d debugging) Println(args ...interface{}) { - if d { - fmt.Println(append([]interface{}{"DEBUG:"}, args...)...) - } -} - -func (d debugging) Printf(format string, args ...interface{}) { - if d { - fmt.Printf("DEBUG: "+format, args...) - } -} diff --git a/misc/logos/types.go b/misc/logos/types.go index 944f02515d3..96e983992eb 100644 --- a/misc/logos/types.go +++ b/misc/logos/types.go @@ -7,7 +7,7 @@ import ( "github.com/gdamore/tcell/v2" ) -//---------------------------------------- +// ---------------------------------------- // Page // A Page has renderable Elem(ents). @@ -389,7 +389,7 @@ func (pg *Page) DecCursor(isVertical bool) { } } -//---------------------------------------- +// ---------------------------------------- // TextElem type TextElem struct { @@ -474,8 +474,6 @@ func (tel *TextElem) Render() (updated bool) { return true } -var ctr = 0 - func (tel *TextElem) Draw(offset Coord, view View) { minX, maxX, minY, maxY := computeIntersection(tel.Size, offset, view.Bounds) for y := minY; y < maxY; y++ { @@ -494,7 +492,7 @@ func (tel *TextElem) ProcessEventKey(ev *EventKey) bool { return false // TODO: clipboard. } -//---------------------------------------- +// ---------------------------------------- // misc. type Color = tcell.Color @@ -728,7 +726,7 @@ func (tt *Attrs) Merge(ot *Attrs) { tt.Other = ot.Other // TODO merge by key. } -//---------------------------------------- +// ---------------------------------------- // AttrFlags // NOTE: AttrFlags are merged with a simple or-assign op. @@ -752,7 +750,7 @@ type KVPair struct { Value interface{} } -//---------------------------------------- +// ---------------------------------------- // computeIntersection() // els: element size @@ -812,7 +810,7 @@ func computeIntersection(els Size, elo Coord, vws Size) (minX, maxX, minY, maxY return } -//---------------------------------------- +// ---------------------------------------- // Misc simple types type Padding struct { diff --git a/misc/logos/unicode.go b/misc/logos/unicode.go index 3bdb46cd88b..924edecc2c5 100644 --- a/misc/logos/unicode.go +++ b/misc/logos/unicode.go @@ -4,7 +4,7 @@ func isCombining(r rune) bool { return inTable(r, combining) } -//---------------------------------------- +// ---------------------------------------- // from https://github.com/mattn/go-runewidth // runewidth doesn't expose whether a character is combining or not. // TODO might as well fork both runewidth and tcell. @@ -62,15 +62,6 @@ type interval struct { type table []interval -func inTables(r rune, ts ...table) bool { - for _, t := range ts { - if inTable(r, t) { - return true - } - } - return false -} - func inTable(r rune, t table) bool { if r < t[0].first { return false diff --git a/tm2/README.md b/tm2/README.md index 101fa793e82..c4d6aa8d287 100644 --- a/tm2/README.md +++ b/tm2/README.md @@ -22,7 +22,7 @@ * Minimal code - keep total footprint small. * Minimal dependencies - all dependencies must get audited, and become part of the repo. -* Modular dependencies - whereever reasonable, make components modular. +* Modular dependencies - wherever reasonable, make components modular. * Completeness - software projects that don't become finished are projects that are forever vulnerable. One of the primary goals of the Gno language and related works is to become finished within a reasonable timeframe. diff --git a/tm2/pkg/amino/amino_test.go b/tm2/pkg/amino/amino_test.go index a06601a43da..d7dd0dc5b98 100644 --- a/tm2/pkg/amino/amino_test.go +++ b/tm2/pkg/amino/amino_test.go @@ -10,6 +10,8 @@ import ( ) func TestMarshal(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() type SimpleStruct struct { @@ -35,6 +37,8 @@ func TestMarshal(t *testing.T) { } func TestUnmarshalReader(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() type SimpleStruct struct { @@ -65,6 +69,8 @@ type stringWrapper struct { } func TestUnmarshalReaderSize(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() s1 := stringWrapper{"foo"} @@ -82,6 +88,8 @@ func TestUnmarshalReaderSize(t *testing.T) { } func TestUnmarshalReaderSizeLimit(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() s1 := stringWrapper{"foo"} @@ -101,6 +109,8 @@ func TestUnmarshalReaderSizeLimit(t *testing.T) { } func TestUnmarshalReaderTooLong(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() type SimpleStruct struct { @@ -125,6 +135,8 @@ func TestUnmarshalReaderTooLong(t *testing.T) { } func TestUnmarshalBufferedWritesReads(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() buf := bytes.NewBuffer(nil) @@ -155,6 +167,8 @@ func TestUnmarshalBufferedWritesReads(t *testing.T) { } func TestBoolPointers(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() type SimpleStruct struct { BoolPtrTrue *bool diff --git a/tm2/pkg/amino/binary_decode.go b/tm2/pkg/amino/binary_decode.go index 8ac4161cede..333994d60b0 100644 --- a/tm2/pkg/amino/binary_decode.go +++ b/tm2/pkg/amino/binary_decode.go @@ -12,14 +12,6 @@ const bdOptionByte = 0x01 // ---------------------------------------- // cdc.decodeReflectBinary -var ErrOverflowInt = errors.New("encoded integer value overflows int(32)") - -const ( - // architecture dependent int limits: - maxInt = int(^uint(0) >> 1) - minInt = -maxInt - 1 -) - // This is the main entrypoint for decoding all types from binary form. This // function calls decodeReflectBinary*, and generally those functions should // only call this one, for overrides all happen here. diff --git a/tm2/pkg/amino/binary_encode_test.go b/tm2/pkg/amino/binary_encode_test.go index 8208bb74c67..a775a911cf6 100644 --- a/tm2/pkg/amino/binary_encode_test.go +++ b/tm2/pkg/amino/binary_encode_test.go @@ -8,6 +8,8 @@ import ( ) func TestEncodeFieldNumberAndTyp3_1(t *testing.T) { + t.Parallel() + buf := new(bytes.Buffer) err := encodeFieldNumberAndTyp3(buf, 1, Typ3ByteLength) assert.Nil(t, err) @@ -15,6 +17,8 @@ func TestEncodeFieldNumberAndTyp3_1(t *testing.T) { } func TestEncodeFieldNumberAndTyp3_2(t *testing.T) { + t.Parallel() + buf := new(bytes.Buffer) err := encodeFieldNumberAndTyp3(buf, 2, Typ3ByteLength) assert.Nil(t, err) diff --git a/tm2/pkg/amino/binary_test.go b/tm2/pkg/amino/binary_test.go index 8516ac2e1fa..e50427afeef 100644 --- a/tm2/pkg/amino/binary_test.go +++ b/tm2/pkg/amino/binary_test.go @@ -12,6 +12,8 @@ import ( ) func TestNilSliceEmptySlice(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() type TestStruct struct { @@ -59,6 +61,8 @@ func TestNilSliceEmptySlice(t *testing.T) { } func TestNewFieldBackwardsCompatibility(t *testing.T) { + t.Parallel() + type V1 struct { String string String2 string @@ -109,6 +113,8 @@ func TestNewFieldBackwardsCompatibility(t *testing.T) { } func TestWriteEmpty(t *testing.T) { + t.Parallel() + type Inner struct { Val int } @@ -136,6 +142,8 @@ func TestWriteEmpty(t *testing.T) { } func TestForceWriteEmpty(t *testing.T) { + t.Parallel() + type InnerWriteEmpty struct { // sth. that isn't zero-len if default, e.g. fixed32: ValIn int32 `amino:"write_empty" binary:"fixed32"` @@ -158,6 +166,8 @@ func TestForceWriteEmpty(t *testing.T) { } func TestStructSlice(t *testing.T) { + t.Parallel() + type Foo struct { A uint B uint @@ -182,6 +192,8 @@ func TestStructSlice(t *testing.T) { } func TestStructPointerSlice1(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() type Foo struct { @@ -220,6 +232,8 @@ func TestStructPointerSlice1(t *testing.T) { // Like TestStructPointerSlice2, but without nil_elements field tag. func TestStructPointerSlice2(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() type Foo struct { @@ -251,6 +265,8 @@ func TestStructPointerSlice2(t *testing.T) { } func TestBasicTypes(t *testing.T) { + t.Parallel() + // we explicitly disallow type definitions like the following: type byteAlias []byte @@ -268,6 +284,8 @@ func TestBasicTypes(t *testing.T) { } func TestUnmarshalMapBinary(t *testing.T) { + t.Parallel() + obj := new(map[string]int) cdc := amino.NewCodec() @@ -291,6 +309,8 @@ func TestUnmarshalMapBinary(t *testing.T) { } func TestUnmarshalFuncBinary(t *testing.T) { + t.Parallel() + obj := func() {} cdc := amino.NewCodec() // Binary doesn't support decoding to a func... @@ -316,6 +336,8 @@ func TestUnmarshalFuncBinary(t *testing.T) { } func TestDuration(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() d0 := time.Duration(0) bz := cdc.MustMarshal(d0) diff --git a/tm2/pkg/amino/byteslice_test.go b/tm2/pkg/amino/byteslice_test.go index 310b294a8b7..80faca8e4e9 100644 --- a/tm2/pkg/amino/byteslice_test.go +++ b/tm2/pkg/amino/byteslice_test.go @@ -6,6 +6,8 @@ import ( ) func TestReadByteSliceEquality(t *testing.T) { + t.Parallel() + var encoded []byte var err error cdc := NewCodec() diff --git a/tm2/pkg/amino/codec_test.go b/tm2/pkg/amino/codec_test.go index 7e025f73271..9368d4ef40e 100644 --- a/tm2/pkg/amino/codec_test.go +++ b/tm2/pkg/amino/codec_test.go @@ -29,6 +29,8 @@ func newSimpleStruct() SimpleStruct { } func TestMarshalUnmarshalPointer0(t *testing.T) { + t.Parallel() + s := newSimpleStruct() cdc := amino.NewCodec() b, err := cdc.MarshalSized(s) // no indirection @@ -41,6 +43,8 @@ func TestMarshalUnmarshalPointer0(t *testing.T) { } func TestMarshalUnmarshalPointer1(t *testing.T) { + t.Parallel() + s := newSimpleStruct() cdc := amino.NewCodec() b, err := cdc.MarshalSized(&s) // extra indirection @@ -53,6 +57,8 @@ func TestMarshalUnmarshalPointer1(t *testing.T) { } func TestMarshalUnmarshalPointer2(t *testing.T) { + t.Parallel() + s := newSimpleStruct() ptr := &s cdc := amino.NewCodec() @@ -63,6 +69,8 @@ func TestMarshalUnmarshalPointer2(t *testing.T) { } func TestMarshalUnmarshalPointer3(t *testing.T) { + t.Parallel() + s := newSimpleStruct() cdc := amino.NewCodec() b, err := cdc.MarshalSized(s) // no indirection @@ -75,6 +83,8 @@ func TestMarshalUnmarshalPointer3(t *testing.T) { } func TestDecodeVarint8(t *testing.T) { + t.Parallel() + // DecodeVarint8 uses binary.Varint so we need to make // sure that all the values out of the range of [-128, 127] // return an error. @@ -122,6 +132,8 @@ func TestDecodeVarint8(t *testing.T) { } func TestDecodeVarint16(t *testing.T) { + t.Parallel() + // DecodeVarint16 uses binary.Varint so we need to make // sure that all the values out of the range of [-32768, 32767] // return an error. @@ -170,6 +182,8 @@ func TestDecodeVarint16(t *testing.T) { } func TestEncodeDecodeString(t *testing.T) { + t.Parallel() + s := "🔌🎉⛵︎♠️⎍" bs := []byte(s) di := len(bs) * 3 / 4 @@ -214,8 +228,7 @@ func TestEncodeDecodeString(t *testing.T) { } func TestCodecSeal(t *testing.T) { - type Foo interface{} - type Bar interface{} + t.Parallel() cdc := amino.NewCodec() cdc.Seal() diff --git a/tm2/pkg/amino/deep_copy_test.go b/tm2/pkg/amino/deep_copy_test.go index 55aff4c6a60..f2a793bfca1 100644 --- a/tm2/pkg/amino/deep_copy_test.go +++ b/tm2/pkg/amino/deep_copy_test.go @@ -15,6 +15,8 @@ func (dcf *DCFoo1) MarshalAmino() (string, error) { return dcf.a, nil } func (dcf *DCFoo1) UnmarshalAmino(s string) error { dcf.a = s; return nil } func TestDeepCopyFoo1(t *testing.T) { + t.Parallel() + dcf1 := newDCFoo1("foobar") dcf2 := amino.DeepCopy(dcf1).(*DCFoo1) assert.Equal(t, "foobar", dcf2.a) @@ -27,6 +29,8 @@ func (dcf DCFoo2) MarshalAmino() (string, error) { return dcf.a, nil } // non-p func (dcf *DCFoo2) UnmarshalAmino(s string) error { dcf.a = s; return nil } func TestDeepCopyFoo2(t *testing.T) { + t.Parallel() + dcf1 := newDCFoo2("foobar") dcf2 := amino.DeepCopy(dcf1).(*DCFoo2) assert.Equal(t, "foobar", dcf2.a) @@ -39,6 +43,8 @@ func (dcf DCFoo3) MarshalAmino() (string, error) { return dcf.a, nil } func (dcf *DCFoo3) UnmarshalAmino(s []byte) error { dcf.a = string(s); return nil } // mismatch type func TestDeepCopyFoo3(t *testing.T) { + t.Parallel() + dcf1 := newDCFoo3("foobar") dcf2 := amino.DeepCopy(dcf1).(*DCFoo3) assert.Equal(t, "", dcf2.a) @@ -52,6 +58,8 @@ func (dcf DCFoo4) MarshalAmino() (string, error) { return dcf.a, nil } func (dcf *DCFoo4) UnmarshalAmino(s string) error { dcf.a = s; return nil } // mismatch type func TestDeepCopyFoo4(t *testing.T) { + t.Parallel() + dcf1 := newDCFoo4("foobar") dcf2 := amino.DeepCopy(dcf1).(*DCFoo4) assert.Equal(t, "good", dcf2.a) @@ -65,6 +73,8 @@ func (dcf DCFoo5) MarshalAmino() (string, error) { return dcf.a, nil } func (dcf *DCFoo5) UnmarshalAmino(s string) error { dcf.a = s; return nil } // mismatch type func TestDeepCopyFoo5(t *testing.T) { + t.Parallel() + dcf1 := newDCFoo5("foobar") dcf2 := amino.DeepCopy(dcf1).(*DCFoo5) assert.Equal(t, "good", dcf2.a) @@ -76,6 +86,8 @@ func newDCFoo6(a string) *DCFoo6 { return &DCFoo6{a: a} } func (dcf *DCFoo6) DeepCopy() DCFoo6 { return DCFoo6{"good"} } func TestDeepCopyFoo6(t *testing.T) { + t.Parallel() + dcf1 := newDCFoo6("foobar") dcf2 := amino.DeepCopy(dcf1).(*DCFoo6) assert.Equal(t, "good", dcf2.a) @@ -87,6 +99,8 @@ func newDCFoo7(a string) *DCFoo7 { return &DCFoo7{a: a} } func (dcf DCFoo7) DeepCopy() *DCFoo7 { return &DCFoo7{"good"} } func TestDeepCopyFoo7(t *testing.T) { + t.Parallel() + dcf1 := newDCFoo7("foobar") dcf2 := amino.DeepCopy(dcf1).(*DCFoo7) assert.Equal(t, "good", dcf2.a) @@ -99,6 +113,8 @@ func (dcf DCFoo8) MarshalAmino() (string, error) { return "", errors.New("uh oh func (dcf *DCFoo8) UnmarshalAmino(s string) error { dcf.a = s; return nil } func TestDeepCopyFoo8(t *testing.T) { + t.Parallel() + dcf1 := newDCFoo8("foobar") assert.Panics(t, func() { amino.DeepCopy(dcf1) }) } @@ -110,6 +126,8 @@ func (dcf DCFoo9) MarshalAmino() (string, error) { return dcf.a, nil } func (dcf *DCFoo9) UnmarshalAmino(s string) error { return errors.New("uh oh") } // error func TestDeepCopyFoo9(t *testing.T) { + t.Parallel() + dcf1 := newDCFoo9("foobar") assert.Panics(t, func() { amino.DeepCopy(dcf1) }) } @@ -119,12 +137,16 @@ type DCInterface1 struct { } func TestDeepCopyInterface1(t *testing.T) { + t.Parallel() + dci1 := DCInterface1{Foo: nil} dci2 := amino.DeepCopy(dci1).(DCInterface1) assert.Nil(t, dci2.Foo) } func TestDeepCopyInterface2(t *testing.T) { + t.Parallel() + dci1 := DCInterface1{Foo: "foo"} dci2 := amino.DeepCopy(dci1).(DCInterface1) assert.Equal(t, "foo", dci2.Foo) diff --git a/tm2/pkg/amino/encoder_test.go b/tm2/pkg/amino/encoder_test.go index 5c8afc4833d..02ece6fd667 100644 --- a/tm2/pkg/amino/encoder_test.go +++ b/tm2/pkg/amino/encoder_test.go @@ -7,6 +7,8 @@ import ( ) func TestUvarintSize(t *testing.T) { + t.Parallel() + testCases := []struct { name string u uint64 @@ -22,7 +24,10 @@ func TestUvarintSize(t *testing.T) { {"64 bits", 1 << 63, 10}, } for i, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tc.want, UvarintSize(tc.u), "#%d", i) //nolint:scopelint }) } diff --git a/tm2/pkg/amino/gengo/gengo_test.go b/tm2/pkg/amino/gengo/gengo_test.go index 88b25d9acdd..404b1c77c23 100644 --- a/tm2/pkg/amino/gengo/gengo_test.go +++ b/tm2/pkg/amino/gengo/gengo_test.go @@ -15,6 +15,8 @@ type SampleStruct struct { } func TestBasic(t *testing.T) { + t.Parallel() + p := press.NewPress() fmt.Println(p) ss := SampleStruct{"cat", "dog"} diff --git a/tm2/pkg/amino/genproto/bindings.go b/tm2/pkg/amino/genproto/bindings.go index 458d8b14578..5d587870a7d 100644 --- a/tm2/pkg/amino/genproto/bindings.go +++ b/tm2/pkg/amino/genproto/bindings.go @@ -105,7 +105,7 @@ func generateMethodsForType(imports *ast.GenDecl, scope *ast.Scope, pkg *amino.P } dpbote_ := pbote_[1:] - ////////////////// + // ----------- // ToPBMessage() { scope2 := ast.NewScope(scope) @@ -127,7 +127,7 @@ func generateMethodsForType(imports *ast.GenDecl, scope *ast.Scope, pkg *amino.P )) } - ////////////////// + // ----------- // EmptyPBMessage() // Use to create the pbm to proto.Unmarshal to before FromPBMessage. { @@ -148,7 +148,7 @@ func generateMethodsForType(imports *ast.GenDecl, scope *ast.Scope, pkg *amino.P )) } - ////////////////// + // ----------- // FromPBMessage() { scope2 := ast.NewScope(scope) @@ -169,7 +169,7 @@ func generateMethodsForType(imports *ast.GenDecl, scope *ast.Scope, pkg *amino.P )) } - ////////////////// + // ----------- // TypeUrl() { methods = append(methods, _func("GetTypeURL", @@ -182,7 +182,7 @@ func generateMethodsForType(imports *ast.GenDecl, scope *ast.Scope, pkg *amino.P )) } - ////////////////// + // ----------- // Is*ReprEmpty() { rinfo := info.ReprType @@ -965,7 +965,7 @@ func isReprEmptyStmts(rootPkg *amino.Package, isRoot bool, imports *ast.GenDecl, return b } -//---------------------------------------- +// ---------------------------------------- // other.... // Splits a Go expression into left and right parts. @@ -1013,7 +1013,7 @@ func chopRight(expr string) (left string, tok rune, right string) { return } -//---------------------------------------- +// ---------------------------------------- // AST Construction (Expr) func _i(name string) *ast.Ident { @@ -1023,14 +1023,6 @@ func _i(name string) *ast.Ident { return &ast.Ident{Name: name} } -func _iOrNil(name string) *ast.Ident { - if name == "" { - return nil - } else { - return _i(name) - } -} - // recvTypeName is empty if there are no receivers. // recvTypeName cannot contain any dots. func _func(name string, recvRef string, recvTypeName string, params *ast.FieldList, results *ast.FieldList, b *ast.BlockStmt) *ast.FuncDecl { @@ -1344,29 +1336,6 @@ func _x(expr string, args ...interface{}) ast.Expr { // 3 == != < <= > >= // 2 && // 1 || -var sp = " " - -var ( - prec5 = strings.Split("* / % << >> & &^", sp) - prec4 = strings.Split("+ - | ^", sp) - prec3 = strings.Split("== != < <= > >=", sp) - prec2 = strings.Split("&&", sp) - prec1 = strings.Split("||", sp) - precs = [][]string{prec1, prec2, prec3, prec4, prec5} -) - -// 0 for prec1... -1 if no match. -func lowestMatch(op string) int { - for i, prec := range precs { - for _, op2 := range prec { - if op == op2 { - return i - } - } - } - return -1 -} - func _kv(k, v interface{}) *ast.KeyValueExpr { var kx, vx ast.Expr if ks, ok := k.(string); ok { @@ -1391,10 +1360,6 @@ func _block(b ...ast.Stmt) *ast.BlockStmt { } } -func _xs(exprs ...ast.Expr) []ast.Expr { - return exprs -} - // Usage: _a(lhs1, lhs2, ..., ":=", rhs1, rhs2, ...) // Token can be ":=", "=", "+=", etc. // Other strings are automatically parsed as _x(arg). @@ -1470,13 +1435,6 @@ func _call(fn ast.Expr, args ...ast.Expr) *ast.CallExpr { } } -func _ta(x ast.Expr, t ast.Expr) *ast.TypeAssertExpr { - return &ast.TypeAssertExpr{ - X: x, - Type: t, - } -} - func _sel(x ast.Expr, sel string) *ast.SelectorExpr { return &ast.SelectorExpr{ X: x, @@ -1532,7 +1490,7 @@ func _sl(x ast.Expr) *ast.ArrayType { } } -//---------------------------------------- +// ---------------------------------------- // AST Construction (Stmt) func _if(cond ast.Expr, b ...ast.Stmt) *ast.IfStmt { @@ -1564,34 +1522,6 @@ func _return(results ...ast.Expr) *ast.ReturnStmt { } } -func _continue(label string) *ast.BranchStmt { - return &ast.BranchStmt{ - Tok: token.CONTINUE, - Label: _i(label), - } -} - -func _break(label string) *ast.BranchStmt { - return &ast.BranchStmt{ - Tok: token.BREAK, - Label: _i(label), - } -} - -func _goto(label string) *ast.BranchStmt { - return &ast.BranchStmt{ - Tok: token.GOTO, - Label: _i(label), - } -} - -func _fallthrough(label string) *ast.BranchStmt { - return &ast.BranchStmt{ - Tok: token.FALLTHROUGH, - Label: _i(label), - } -} - // even/odd args are paired, // name1, path1, name2, path2, etc. func _imports(nameAndPaths ...string) *ast.GenDecl { @@ -1618,15 +1548,6 @@ func _for(init ast.Stmt, cond ast.Expr, post ast.Stmt, b ...ast.Stmt) *ast.ForSt } } -func _loop(b ...ast.Stmt) *ast.ForStmt { - return _for(nil, nil, nil, b...) -} - -func _once(b ...ast.Stmt) *ast.ForStmt { - b = append(b, _break("")) - return _for(nil, nil, nil, b...) -} - func _len(x ast.Expr) *ast.CallExpr { return _call(_i("len"), x) } @@ -1766,7 +1687,7 @@ func _aop(op string) token.Token { } } -//---------------------------------------- +// ---------------------------------------- // AST Compile-Time func _ctif(cond bool, then_, else_ ast.Stmt) ast.Stmt { @@ -1779,7 +1700,7 @@ func _ctif(cond bool, then_, else_ ast.Stmt) ast.Stmt { } } -//---------------------------------------- +// ---------------------------------------- // AST query and manipulation. func importPathForName(name string, imports *ast.GenDecl) (path string, exists bool) { @@ -1800,24 +1721,6 @@ func importPathForName(name string, imports *ast.GenDecl) (path string, exists b return "", false } -func importNameForPath(path string, imports *ast.GenDecl) (name string, exists bool) { - if imports.Tok != token.IMPORT { - panic("unexpected ast.GenDecl token " + imports.Tok.String()) - } - for _, spec := range imports.Specs { - if ispec, ok := spec.(*ast.ImportSpec); ok { - specPath, err := strconv.Unquote(ispec.Path.Value) - if err != nil { - panic("malformed path " + ispec.Path.Value) - } - if specPath == path { - return ispec.Name.Name, true - } - } - } - return "", false -} - func rootScope(scope *ast.Scope) *ast.Scope { for scope.Outer != nil { scope = scope.Outer diff --git a/tm2/pkg/amino/genproto/bindings_test.go b/tm2/pkg/amino/genproto/bindings_test.go index 295c388def3..d1911d43542 100644 --- a/tm2/pkg/amino/genproto/bindings_test.go +++ b/tm2/pkg/amino/genproto/bindings_test.go @@ -11,6 +11,8 @@ import ( ) func TestGenerateProtoBindings(t *testing.T) { + t.Parallel() + file, err := GenerateProtoBindingsForTypes(tests.Package, tests.Package.ReflectTypes()...) assert.NoError(t, err) t.Logf("%v", file) diff --git a/tm2/pkg/amino/genproto/example/main.go b/tm2/pkg/amino/genproto/example/main.go index 24132305893..6e4023b596d 100644 --- a/tm2/pkg/amino/genproto/example/main.go +++ b/tm2/pkg/amino/genproto/example/main.go @@ -11,16 +11,12 @@ import ( // amino type StructA struct { - fieldA int - fieldB int FieldC int FieldD uint32 } // amino type StructB struct { - fieldA int - fieldB int FieldC int FieldD uint32 FieldE submodule.StructSM diff --git a/tm2/pkg/amino/genproto/genproto.go b/tm2/pkg/amino/genproto/genproto.go index 8af73b2690a..4f7154e058c 100644 --- a/tm2/pkg/amino/genproto/genproto.go +++ b/tm2/pkg/amino/genproto/genproto.go @@ -6,7 +6,6 @@ import ( "bytes" "errors" "fmt" - "io/ioutil" "os" "os/exec" "path" @@ -120,7 +119,6 @@ func (p3c *P3Context) GetP3ImportPath(p3type P3Type, implicit bool) string { func (p3c *P3Context) GenerateProto3MessagePartial(p3doc *P3Doc, rt reflect.Type) (p3msg P3Message) { if p3doc.PackageName == "" { panic(fmt.Sprintf("cannot generate message partials in the root package \"\".")) - return } if rt.Kind() == reflect.Ptr { panic("pointers not yet supported. if you meant pointer-preferred (for decoding), pass in rt.Elem()") @@ -220,7 +218,6 @@ func (p3c *P3Context) GenerateProto3MessagePartial(p3doc *P3Doc, rt reflect.Type func (p3c *P3Context) GenerateProto3ListPartial(p3doc *P3Doc, nl NList) (p3msg P3Message) { if p3doc.PackageName == "" { panic(fmt.Sprintf("cannot generate message partials in the root package \"\".")) - return } ep3 := nl.ElemP3Type() @@ -496,7 +493,7 @@ func RunProtoc(pkg *amino.Package, protosDir string) { } } // First generate output to a temp dir. - tempDir, err := ioutil.TempDir("", "amino-genproto") + tempDir, err := os.MkdirTemp("", "amino-genproto") if err != nil { return } diff --git a/tm2/pkg/amino/genproto/genproto_test.go b/tm2/pkg/amino/genproto/genproto_test.go index 20a45361519..2a52741c901 100644 --- a/tm2/pkg/amino/genproto/genproto_test.go +++ b/tm2/pkg/amino/genproto/genproto_test.go @@ -9,6 +9,8 @@ import ( ) func TestBasic(t *testing.T) { + t.Parallel() + p3c := NewP3Context() p3c.RegisterPackage(sm1.Package) p3doc := P3Doc{PackageName: "test"} diff --git a/tm2/pkg/amino/genproto/scanner.go b/tm2/pkg/amino/genproto/scanner.go deleted file mode 100644 index 6c5f770ab48..00000000000 --- a/tm2/pkg/amino/genproto/scanner.go +++ /dev/null @@ -1,192 +0,0 @@ -package genproto - -import "fmt" - -type runestate int - -const ( - runestateCode runestate = 0 - runestateRune runestate = 1 - runestateStringQuote runestate = 2 - runestateStringBacktick runestate = 3 -) - -type scanner struct { - str string - rnz []rune - idx int - runestate - curly int - round int - square int -} - -// returns a new scanner. -func newScanner(str string) *scanner { - rnz := make([]rune, 0, len(str)) - for _, r := range str { - rnz = append(rnz, r) - } - return &scanner{ - str: str, - runestate: runestateCode, - rnz: rnz, - } -} - -// Peeks the next n runes and returns a string. returns a shorter string if -// there are less than n runes left. -func (ss *scanner) peek(n int) string { - if ss.idx+n > len(ss.rnz) { - return string(ss.rnz[ss.idx:len(ss.rnz)]) - } - return string(ss.rnz[ss.idx : ss.idx+n]) -} - -// Advance a single rune, e.g. by incrementing ss.curly if ss.rnz[ss.idx] is -// '{' before advancing. If ss.runestate is runestateRune or runestateQuote, -// advances escape sequences to completion so ss.idx may increment more than -// one. Returns true if done. -func (ss *scanner) advance() bool { - rn := ss.rnz[ss.idx] // just panic if out of scope, caller error. - switch ss.runestate { - case runestateCode: - switch rn { - case '}': - ss.curly-- - if ss.curly < 0 { - panic("mismatched curly: " + ss.str) - } - case ')': - ss.round-- - if ss.round < 0 { - panic("mismatched round: " + ss.str) - } - case ']': - ss.square-- - if ss.square < 0 { - panic("mismatched square: " + ss.str) - } - case '{': - ss.curly++ - case '(': - ss.round++ - case '[': - ss.square++ - case '\'': - ss.runestate = runestateRune - case '"': - ss.runestate = runestateStringQuote - case '`': - ss.runestate = runestateStringBacktick - } - case runestateRune: - switch rn { - case '\\': - return ss.advanceEscapeSequence() - case '\'': - ss.runestate = runestateCode - } - case runestateStringQuote: - switch rn { - case '\\': - return ss.advanceEscapeSequence() - case '"': - ss.runestate = runestateCode - } - case runestateStringBacktick: - switch rn { - case '`': - ss.runestate = runestateCode - } - } - ss.idx++ - return ss.done() -} - -// returns true if no runes left to advance. -func (ss *scanner) done() bool { - return ss.idx == len(ss.rnz) -} - -// returns true if outside the scope of any -// parentheses, brackets, strings, or rune literals. -func (ss *scanner) out() bool { - return ss.runestate == runestateCode && - ss.curly == int(0) && - ss.round == int(0) && - ss.square == int(0) -} - -func isOctal(r rune) bool { - switch r { - case '0', '1', '2', '3', '4', '5', '6', '7': - return true - default: - return false - } -} - -func isHex(r rune) bool { - switch r { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'a', 'b', 'c', 'd', 'e', 'f', - 'A', 'B', 'C', 'D', 'E', 'F': - return true - default: - return false - } -} - -// Advances runes, while checking that each passes `check`. if error, panics -// with info including `back` runes back. -func (ss *scanner) eatRunes(back int, eat int, check func(rune) bool) { - for i := 0; i < eat; i++ { - if ss.idx+i == len(ss.rnz) { - panic(fmt.Sprintf("eof while parsing: %s", - string(ss.rnz[ss.idx-back:]))) - } - if !check(ss.rnz[ss.idx+i]) { - panic(fmt.Sprintf("invalid character while parsing: %s", - string(ss.rnz[ss.idx-back:ss.idx+i+1]))) - } - ss.idx++ - } -} - -// increments ss.idx until escape sequence is complete. returns true if done. -func (ss *scanner) advanceEscapeSequence() bool { - rn1 := ss.rnz[ss.idx] - if rn1 != '\\' { - panic("should not happen") - } - if ss.idx == len(ss.rnz)-1 { - panic("eof while parsing escape sequence") - } - rn2 := ss.rnz[ss.idx+1] - switch rn2 { - case 'x': - ss.idx += 2 - ss.eatRunes(2, 2, isHex) - return ss.done() - case 'u': - ss.idx += 2 - ss.eatRunes(2, 4, isHex) - return ss.done() - case 'U': - ss.idx += 2 - ss.eatRunes(2, 8, isHex) - return ss.done() - case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '\'', '"': - ss.idx += 2 - return ss.done() - default: - ss.idx += 1 - if isOctal(rn2) { - ss.eatRunes(1, 3, isOctal) - } else { - panic("invalid escape sequence") - } - return ss.done() - } -} diff --git a/tm2/pkg/amino/genproto/types_test.go b/tm2/pkg/amino/genproto/types_test.go index 8b7398ab184..4e57427e809 100644 --- a/tm2/pkg/amino/genproto/types_test.go +++ b/tm2/pkg/amino/genproto/types_test.go @@ -7,6 +7,8 @@ import ( ) func TestPrintP3Types(t *testing.T) { + t.Parallel() + doc := P3Doc{ Comment: "doc comment", Messages: []P3Message{ diff --git a/tm2/pkg/amino/json_test.go b/tm2/pkg/amino/json_test.go index 81118080d89..e3f7d413fcb 100644 --- a/tm2/pkg/amino/json_test.go +++ b/tm2/pkg/amino/json_test.go @@ -29,6 +29,8 @@ func registerTransports(cdc *amino.Codec) { } func TestMarshalJSON(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() registerTransports(cdc) cases := []struct { @@ -130,6 +132,8 @@ func TestMarshalJSON(t *testing.T) { } func TestMarshalJSONTime(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() registerTransports(cdc) @@ -180,6 +184,8 @@ type innerFP struct { // We don't support maps. func TestUnmarshalMap(t *testing.T) { + t.Parallel() + jsonBytes := []byte("dontcare") obj := new(map[string]int) cdc := amino.NewCodec() @@ -198,6 +204,8 @@ func TestUnmarshalMap(t *testing.T) { } func TestUnmarshalFunc(t *testing.T) { + t.Parallel() + jsonBytes := []byte(`"dontcare"`) obj := func() {} cdc := amino.NewCodec() @@ -218,6 +226,8 @@ func TestUnmarshalFunc(t *testing.T) { } func TestUnmarshalJSON(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() registerTransports(cdc) cases := []struct { @@ -310,6 +320,8 @@ func TestUnmarshalJSON(t *testing.T) { } func TestJSONCodecRoundTrip(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() registerTransports(cdc) type allInclusive struct { @@ -480,6 +492,8 @@ func interfacePtr(v interface{}) *interface{} { // Test to ensure that Amino codec's time encoding/decoding roundtrip // produces the same result as the standard library json's. func TestAminoJSONTimeEncodeDecodeRoundTrip(t *testing.T) { + t.Parallel() + loc, err := time.LoadLocation("America/Los_Angeles") require.NoError(t, err) din := time.Date(2008, 9, 15, 14, 13, 12, 11109876, loc).Round(time.Millisecond).UTC() @@ -503,6 +517,8 @@ func TestAminoJSONTimeEncodeDecodeRoundTrip(t *testing.T) { } func TestMarshalJSONIndent(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() registerTransports(cdc) obj := Transport{Vehicle: Car("Tesla")} diff --git a/tm2/pkg/amino/libs/press/press_test.go b/tm2/pkg/amino/libs/press/press_test.go index 3978e5a5f52..e05042f4556 100644 --- a/tm2/pkg/amino/libs/press/press_test.go +++ b/tm2/pkg/amino/libs/press/press_test.go @@ -7,11 +7,15 @@ import ( ) func TestEmpty(t *testing.T) { + t.Parallel() + p := NewPress() assert.Equal(t, p.Print(), "") } func TestBasic(t *testing.T) { + t.Parallel() + p := NewPress() p.P("this ") p.P("is ") @@ -20,6 +24,8 @@ func TestBasic(t *testing.T) { } func TestBasicLn(t *testing.T) { + t.Parallel() + p := NewPress() p.P("this ") p.P("is ") @@ -28,6 +34,8 @@ func TestBasicLn(t *testing.T) { } func TestNewlineStr(t *testing.T) { + t.Parallel() + p := NewPress().SetNewlineStr("\r\n") p.P("this ") p.P("is ") @@ -38,6 +46,8 @@ func TestNewlineStr(t *testing.T) { } func TestIndent(t *testing.T) { + t.Parallel() + p := NewPress() p.P("first line ") p.Pl("{").I(func(p *Press) { @@ -51,6 +61,8 @@ func TestIndent(t *testing.T) { } func TestIndent2(t *testing.T) { + t.Parallel() + p := NewPress() p.P("first line ") p.Pl("{").I(func(p *Press) { @@ -66,6 +78,8 @@ func TestIndent2(t *testing.T) { } func TestIndent3(t *testing.T) { + t.Parallel() + p := NewPress() p.P("first line ") p.Pl("{").I(func(p *Press) { @@ -78,6 +92,8 @@ func TestIndent3(t *testing.T) { } func TestIndentLn(t *testing.T) { + t.Parallel() + p := NewPress() p.P("first line ") p.Pl("{").I(func(p *Press) { @@ -92,6 +108,8 @@ func TestIndentLn(t *testing.T) { } func TestNestedIndent(t *testing.T) { + t.Parallel() + p := NewPress() p.P("first line ") p.Pl("{").I(func(p *Press) { diff --git a/tm2/pkg/amino/pkg/pkg_test.go b/tm2/pkg/amino/pkg/pkg_test.go index 2be0b0cefe3..1be87e88f4e 100644 --- a/tm2/pkg/amino/pkg/pkg_test.go +++ b/tm2/pkg/amino/pkg/pkg_test.go @@ -14,6 +14,8 @@ type Foo struct { } func TestNewPackage(t *testing.T) { + t.Parallel() + // This should panic, as slashes in p3pkg is not allowed. assert.Panics(t, func() { NewPackage("foobar.com/some/path", "some/path", "").WithTypes(Foo{}) @@ -39,6 +41,8 @@ func TestNewPackage(t *testing.T) { } func TestFullNameForType(t *testing.T) { + t.Parallel() + // The Go package depends on how this test is invoked. // Sometimes it is "github.com/gnolang/gno/tm2/pkg/amino/packagepkg_test". // Sometimes it is "command-line-arguments" @@ -55,6 +59,8 @@ func TestFullNameForType(t *testing.T) { // If the struct wasn't registered, you can't get a name or type_url for it. func TestFullNameForUnexpectedType(t *testing.T) { + t.Parallel() + gopkg := reflect.TypeOf(Foo{}).PkgPath() pkg := NewPackage(gopkg, "some.path", "") diff --git a/tm2/pkg/amino/reflect.go b/tm2/pkg/amino/reflect.go index bc4fa57e626..01402a320d2 100644 --- a/tm2/pkg/amino/reflect.go +++ b/tm2/pkg/amino/reflect.go @@ -1,26 +1,21 @@ package amino import ( - "encoding/json" "fmt" "reflect" "unicode" ) -//---------------------------------------- +// ---------------------------------------- // Constants -var ( - jsonMarshalerType = reflect.TypeOf(new(json.Marshaler)).Elem() - jsonUnmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem() - errorType = reflect.TypeOf(new(error)).Elem() -) +var errorType = reflect.TypeOf(new(error)).Elem() -//---------------------------------------- +// ---------------------------------------- // encode: see binary-encode.go and json-encode.go // decode: see binary-decode.go and json-decode.go -//---------------------------------------- +// ---------------------------------------- // Misc. // CONTRACT: by the time this is called, len(bz) >= _n @@ -182,18 +177,6 @@ func constructConcreteType(cinfo *TypeInfo) (crv, irvSet reflect.Value) { return } -// Like constructConcreteType(), but if pointer preferred, returns a nil one. -// We like nil pointers for efficiency. -func constructConcreteTypeNilPreferred(cinfo *TypeInfo) (crv reflect.Value) { - // Construct new concrete type. - if cinfo.PointerPreferred { - crv = reflect.Zero(cinfo.PtrToType) - } else { - crv = reflect.New(cinfo.Type).Elem() - } - return -} - func toReprObject(rv reflect.Value) (rrv reflect.Value, err error) { var mwrm reflect.Value if rv.CanAddr() { @@ -269,12 +252,6 @@ func unmarshalAminoReprType(rm reflect.Method) (rrt reflect.Type) { return } -func toPBMessage(cdc *Codec, rv reflect.Value) (pbrv reflect.Value) { - rm := rv.MethodByName("ToPBMessage") - pbrv = rm.Call([]reflect.Value{reflect.ValueOf(cdc)})[0] - return -} - // NOTE: do not change this definition. // It is also defined for genproto. func isListType(rt reflect.Type) bool { diff --git a/tm2/pkg/amino/reflect_test.go b/tm2/pkg/amino/reflect_test.go index c5c7b6b5c83..9adfa0b35d0 100644 --- a/tm2/pkg/amino/reflect_test.go +++ b/tm2/pkg/amino/reflect_test.go @@ -18,44 +18,70 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino/tests" ) -//------------------------------------- +// ------------------------------------- // Non-interface Google fuzz tests func TestCodecStruct(t *testing.T) { + t.Parallel() + for _, ptr := range tests.StructTypes { t.Logf("case %v", reflect.TypeOf(ptr)) rt := getTypeFromPointer(ptr) name := rt.Name() - t.Run(name+":binary", func(t *testing.T) { _testCodec(t, rt, "binary") }) - t.Run(name+":json", func(t *testing.T) { _testCodec(t, rt, "json") }) + t.Run(name+":binary", func(t *testing.T) { + t.Parallel() + _testCodec(t, rt, "binary") + }) + t.Run(name+":json", func(t *testing.T) { + t.Parallel() + _testCodec(t, rt, "json") + }) } } func TestCodecDef(t *testing.T) { + t.Parallel() + for _, ptr := range tests.DefTypes { t.Logf("case %v", reflect.TypeOf(ptr)) rt := getTypeFromPointer(ptr) name := rt.Name() - t.Run(name+":binary", func(t *testing.T) { _testCodec(t, rt, "binary") }) - t.Run(name+":json", func(t *testing.T) { _testCodec(t, rt, "json") }) + t.Run(name+":binary", func(t *testing.T) { + t.Parallel() + _testCodec(t, rt, "binary") + }) + t.Run(name+":json", func(t *testing.T) { + t.Parallel() + _testCodec(t, rt, "json") + }) } } func TestDeepCopyStruct(t *testing.T) { + t.Parallel() + for _, ptr := range tests.StructTypes { t.Logf("case %v", reflect.TypeOf(ptr)) rt := getTypeFromPointer(ptr) name := rt.Name() - t.Run(name+":deepcopy", func(t *testing.T) { _testDeepCopy(t, rt) }) + t.Run(name+":deepcopy", func(t *testing.T) { + t.Parallel() + _testDeepCopy(t, rt) + }) } } func TestDeepCopyDef(t *testing.T) { + t.Parallel() + for _, ptr := range tests.DefTypes { t.Logf("case %v", reflect.TypeOf(ptr)) rt := getTypeFromPointer(ptr) name := rt.Name() - t.Run(name+":deepcopy", func(t *testing.T) { _testDeepCopy(t, rt) }) + t.Run(name+":deepcopy", func(t *testing.T) { + t.Parallel() + _testDeepCopy(t, rt) + }) } } @@ -190,10 +216,12 @@ func _testDeepCopy(t *testing.T, rt reflect.Type) { } } -//---------------------------------------- +// ---------------------------------------- // Register/interface tests func TestCodecMashalFailsOnUnregisteredConcrete(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() bz, err := cdc.Marshal(struct{ tests.Interface1 }{tests.Concrete1{}}) @@ -202,6 +230,8 @@ func TestCodecMashalFailsOnUnregisteredConcrete(t *testing.T) { } func TestCodecMarshalPassesOnRegistered(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() cdc.RegisterTypeFrom(reflect.TypeOf(tests.Concrete1{}), tests.Package) @@ -218,6 +248,8 @@ func TestCodecMarshalPassesOnRegistered(t *testing.T) { } func TestCodecRegisterAndMarshalMultipleConcrete(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() cdc.RegisterTypeFrom(reflect.TypeOf(tests.Concrete1{}), tests.Package) cdc.RegisterTypeFrom(reflect.TypeOf(tests.Concrete2{}), tests.Package) @@ -251,6 +283,8 @@ func TestCodecRegisterAndMarshalMultipleConcrete(t *testing.T) { // Serialize and deserialize a registered typedef. func TestCodecRoundtripNonNilRegisteredTypeDef(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() cdc.RegisterTypeFrom(reflect.TypeOf(tests.ConcreteTypeDef{}), tests.Package) @@ -322,6 +356,8 @@ func TestCodecRoundtripNonNilRegisteredTypeDef(t *testing.T) { // Exactly like TestCodecRoundtripNonNilRegisteredTypeDef but with struct // around the value instead of a type def. func TestCodecRoundtripNonNilRegisteredWrappedValue(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() cdc.RegisterTypeFrom(reflect.TypeOf(tests.ConcreteWrappedBytes{}), tests.Package) @@ -351,6 +387,8 @@ func TestCodecRoundtripNonNilRegisteredWrappedValue(t *testing.T) { // MarshalAny(msg) and Marshal(&msg) are the same. func TestCodecMarshalAny(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() cdc.RegisterTypeFrom(reflect.TypeOf(tests.ConcreteWrappedBytes{}), tests.Package) @@ -368,6 +406,8 @@ func TestCodecMarshalAny(t *testing.T) { // Like TestCodecRoundtripNonNilRegisteredTypeDef, but JSON. func TestCodecJSONRoundtripNonNilRegisteredTypeDef(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() cdc.RegisterTypeFrom(reflect.TypeOf(tests.ConcreteTypeDef{}), tests.Package) @@ -388,6 +428,8 @@ func TestCodecJSONRoundtripNonNilRegisteredTypeDef(t *testing.T) { // Like TestCodecRoundtripNonNilRegisteredTypeDef, but serialize the concrete value directly. func TestCodecRoundtripMarshalOnConcreteNonNilRegisteredTypeDef(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() cdc.RegisterTypeFrom(reflect.TypeOf(tests.ConcreteTypeDef{}), tests.Package) @@ -418,6 +460,8 @@ func TestCodecRoundtripMarshalOnConcreteNonNilRegisteredTypeDef(t *testing.T) { // Like TestCodecRoundtripNonNilRegisteredTypeDef but read into concrete var. func TestCodecRoundtripUnmarshalOnConcreteNonNilRegisteredTypeDef(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() cdc.RegisterTypeFrom(reflect.TypeOf(tests.ConcreteTypeDef{}), tests.Package) @@ -437,6 +481,8 @@ func TestCodecRoundtripUnmarshalOnConcreteNonNilRegisteredTypeDef(t *testing.T) } func TestCodecBinaryStructFieldNilInterface(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() cdc.RegisterTypeFrom(reflect.TypeOf(tests.InterfaceFieldsStruct{}), tests.Package) @@ -451,7 +497,7 @@ func TestCodecBinaryStructFieldNilInterface(t *testing.T) { require.Equal(t, i2, i1, "i1 and i2 should be the same after decoding") } -//---------------------------------------- +// ---------------------------------------- // Misc. func spw(o interface{}) string { @@ -613,7 +659,7 @@ func getTypeFromPointer(ptr interface{}) reflect.Type { return rt.Elem() } -//---------------------------------------- +// ---------------------------------------- // From https://github.com/google/gofuzz/blob/master/fuzz.go // (Apache2.0 License) diff --git a/tm2/pkg/amino/repr_test.go b/tm2/pkg/amino/repr_test.go index 5fb31515fb6..4be50b5d93d 100644 --- a/tm2/pkg/amino/repr_test.go +++ b/tm2/pkg/amino/repr_test.go @@ -55,6 +55,8 @@ var ( ) func TestMarshalAminoBinary(t *testing.T) { + t.Parallel() + cdc := NewCodec() cdc.RegisterPackage(testPackage) @@ -78,6 +80,8 @@ func TestMarshalAminoBinary(t *testing.T) { } func TestMarshalAminoJSON(t *testing.T) { + t.Parallel() + cdc := NewCodec() cdc.RegisterPackage(testPackage) diff --git a/tm2/pkg/amino/tests/common.go b/tm2/pkg/amino/tests/common.go index b7213a56efc..1abf3aaf601 100644 --- a/tm2/pkg/amino/tests/common.go +++ b/tm2/pkg/amino/tests/common.go @@ -32,8 +32,6 @@ type PrimitivesStruct struct { Time time.Time Duration time.Duration Empty EmptyStruct - - unexposed int8 } type ShortArraysStruct struct { diff --git a/tm2/pkg/amino/tests/proto3/proto3_compat_test.go b/tm2/pkg/amino/tests/proto3/proto3_compat_test.go index bd2a51825da..8f9e04fc35c 100644 --- a/tm2/pkg/amino/tests/proto3/proto3_compat_test.go +++ b/tm2/pkg/amino/tests/proto3/proto3_compat_test.go @@ -36,6 +36,8 @@ func init() { } func TestFixed32Roundtrip(t *testing.T) { + t.Parallel() + // amino fixed32 (int32) <-> protbuf fixed32 (uint32) type testi32 struct { Int32 int32 `binary:"fixed32"` @@ -61,6 +63,8 @@ func TestFixed32Roundtrip(t *testing.T) { } func TestVarintZigzagRoundtrip(t *testing.T) { + t.Parallel() + t.Skip("zigzag encoding isn't default anymore for (unsigned) ints") // amino varint (int) <-> protobuf zigzag32 (int32 in go sint32 in proto file) type testInt32Varint struct { @@ -85,6 +89,8 @@ func TestVarintZigzagRoundtrip(t *testing.T) { } func TestFixedU64Roundtrip(t *testing.T) { + t.Parallel() + type testFixed64Uint struct { Int64 uint64 `binary:"fixed64"` } @@ -111,6 +117,8 @@ func TestFixedU64Roundtrip(t *testing.T) { } func TestMultidimensionalSlices(t *testing.T) { + t.Parallel() + s := [][]int8{ {1, 2}, {3, 4, 5}, @@ -121,6 +129,8 @@ func TestMultidimensionalSlices(t *testing.T) { } func TestMultidimensionalArrays(t *testing.T) { + t.Parallel() + arr := [2][2]int8{ {1, 2}, {3, 4}, @@ -131,6 +141,8 @@ func TestMultidimensionalArrays(t *testing.T) { } func TestMultidimensionalByteArraysAndSlices(t *testing.T) { + t.Parallel() + arr := [2][2]byte{ {1, 2}, {3, 4}, @@ -157,6 +169,8 @@ func TestMultidimensionalByteArraysAndSlices(t *testing.T) { } func TestProto3CompatPtrsRoundtrip(t *testing.T) { + t.Parallel() + s := p3.SomeStruct{} ab, err := cdc.Marshal(s) @@ -214,6 +228,8 @@ type goAminoGotTime struct { } func TestProto3CompatEmptyTimestamp(t *testing.T) { + t.Parallel() + empty := p3.ProtoGotTime{} // protobuf also marshals to empty bytes here: pb, err := proto.Marshal(&empty) @@ -235,6 +251,8 @@ func TestProto3CompatEmptyTimestamp(t *testing.T) { } func TestProto3CompatTimestampNow(t *testing.T) { + t.Parallel() + // test with current time: now := time.Now() ptts, err := ptypes.TimestampProto(now) @@ -267,6 +285,8 @@ func TestProto3CompatTimestampNow(t *testing.T) { } func TestProto3EpochTime(t *testing.T) { + t.Parallel() + pbRes := p3.ProtoGotTime{} // amino encode epoch (1970) and decode using proto; expect the resulting time to be epoch again: ab, err := cdc.Marshal(goAminoGotTime{T: &epoch}) @@ -279,6 +299,8 @@ func TestProto3EpochTime(t *testing.T) { } func TestProtoNegativeSeconds(t *testing.T) { + t.Parallel() + pbRes := p3.ProtoGotTime{} // test with negative seconds (0001-01-01 -> seconds = -62135596800, nanos = 0): ntm, err := time.Parse("2006-01-02 15:04:05 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC") @@ -296,6 +318,8 @@ func TestProtoNegativeSeconds(t *testing.T) { } func TestIntVarintCompat(t *testing.T) { + t.Parallel() + tcs := []struct { val32 int32 val64 int64 @@ -382,6 +406,8 @@ func TestIntVarintCompat(t *testing.T) { // See if encoding of type def types matches the proto3 encoding func TestTypeDefCompatibility(t *testing.T) { + t.Parallel() + pNow := ptypes.TimestampNow() now, err := ptypes.Timestamp(pNow) require.NoError(t, err) diff --git a/tm2/pkg/amino/time2_test.go b/tm2/pkg/amino/time2_test.go index c40e2fc660e..b8d73f37d55 100644 --- a/tm2/pkg/amino/time2_test.go +++ b/tm2/pkg/amino/time2_test.go @@ -18,6 +18,8 @@ type testTime struct { } func TestDecodeSkippedFieldsInTime(t *testing.T) { + t.Parallel() + tm, err := time.Parse("2006-01-02 15:04:05 +0000 UTC", "1970-01-01 00:00:00 +0000 UTC") assert.NoError(t, err) @@ -62,6 +64,8 @@ func TestDecodeSkippedFieldsInTime(t *testing.T) { } func TestMinMaxTimeEncode(t *testing.T) { + t.Parallel() + tMin, err := time.Parse("2006-01-02 15:04:05 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC") assert.NoError(t, err) tm := testTime{tMin} diff --git a/tm2/pkg/amino/wellknown_test.go b/tm2/pkg/amino/wellknown_test.go index 3b91ffc77a5..345466bd285 100644 --- a/tm2/pkg/amino/wellknown_test.go +++ b/tm2/pkg/amino/wellknown_test.go @@ -9,6 +9,8 @@ import ( ) func TestAnyWellKnownNative(t *testing.T) { + t.Parallel() + cdc := amino.NewCodec() s1 := tests.InterfaceFieldsStruct{ diff --git a/tm2/pkg/async/async_test.go b/tm2/pkg/async/async_test.go index f82e32312f0..255ce6b8d51 100644 --- a/tm2/pkg/async/async_test.go +++ b/tm2/pkg/async/async_test.go @@ -12,6 +12,8 @@ import ( ) func TestParallel(t *testing.T) { + t.Parallel() + // Create tasks. counter := new(int32) tasks := make([]Task, 100*1000) @@ -52,6 +54,8 @@ func TestParallel(t *testing.T) { } func TestParallelAbort(t *testing.T) { + t.Parallel() + flow1 := make(chan struct{}, 1) flow2 := make(chan struct{}, 1) flow3 := make(chan struct{}, 1) // Cap must be > 0 to prevent blocking. @@ -103,6 +107,8 @@ func TestParallelAbort(t *testing.T) { } func TestParallelRecover(t *testing.T) { + t.Parallel() + // Create tasks. tasks := []Task{ func(i int) (res interface{}, err error, abort bool) { @@ -155,7 +161,7 @@ func waitTimeout(t *testing.T, taskResultCh TaskResultCh, taskName string) { } else { assert.Fail(t, "TaskResultCh unexpectedly returned for %v", taskName) } - case <-time.After(1 * time.Second): // TODO use deterministic time? + case <-time.After(200 * time.Millisecond): // TODO use deterministic time? // Good! } } diff --git a/tm2/pkg/autofile/autofile_test.go b/tm2/pkg/autofile/autofile_test.go index d50bdca3ce0..d631e0ed265 100644 --- a/tm2/pkg/autofile/autofile_test.go +++ b/tm2/pkg/autofile/autofile_test.go @@ -1,7 +1,6 @@ package autofile import ( - "io/ioutil" "os" "syscall" "testing" @@ -14,7 +13,7 @@ import ( func TestSIGHUP(t *testing.T) { // First, create an AutoFile writing to a tempfile dir - file, err := ioutil.TempFile("", "sighup_test") + file, err := os.CreateTemp("", "sighup_test") require.NoError(t, err) err = file.Close() require.NoError(t, err) @@ -60,7 +59,7 @@ func TestSIGHUP(t *testing.T) { // // Manually modify file permissions, close, and reopen using autofile: // // We expect the file permissions to be changed back to the intended perms. // func TestOpenAutoFilePerms(t *testing.T) { -// file, err := ioutil.TempFile("", "permission_test") +// file, err := os.CreateTemp("", "permission_test") // require.NoError(t, err) // err = file.Close() // require.NoError(t, err) @@ -86,7 +85,7 @@ func TestSIGHUP(t *testing.T) { func TestAutoFileSize(t *testing.T) { // First, create an AutoFile writing to a tempfile dir - f, err := ioutil.TempFile("", "sighup_test") + f, err := os.CreateTemp("", "sighup_test") require.NoError(t, err) err = f.Close() require.NoError(t, err) diff --git a/tm2/pkg/autofile/group.go b/tm2/pkg/autofile/group.go index 3350e1e62c5..189d7818bd8 100644 --- a/tm2/pkg/autofile/group.go +++ b/tm2/pkg/autofile/group.go @@ -125,7 +125,11 @@ func (g *Group) OnStart() error { // OnStop implements service.Service by stopping the goroutine described above. // NOTE: g.Head must be closed separately using Close. func (g *Group) OnStop() { - g.FlushAndSync() + if err := g.FlushAndSync(); err != nil { + g.Logger.Error( + fmt.Sprintf("unable to gracefully flush data, %s", err.Error()), + ) + } } // Wait blocks until all internal goroutines are finished. Supposed to be @@ -136,11 +140,20 @@ func (g *Group) Wait() { // Close closes the head file. The group must be stopped by this moment. func (g *Group) Close() { - g.FlushAndSync() + if err := g.FlushAndSync(); err != nil { + g.Logger.Error( + fmt.Sprintf("unable to gracefully flush data, %s", err.Error()), + ) + } g.mtx.Lock() - _ = g.Head.Close() - g.mtx.Unlock() + defer g.mtx.Unlock() + + if err := g.Head.Close(); err != nil { + g.Logger.Error( + fmt.Sprintf("unable to gracefully close group head, %s", err.Error()), + ) + } } // HeadSizeLimit returns the current head size limit. diff --git a/tm2/pkg/autofile/group_test.go b/tm2/pkg/autofile/group_test.go index 6face4630d1..1bf79894b93 100644 --- a/tm2/pkg/autofile/group_test.go +++ b/tm2/pkg/autofile/group_test.go @@ -47,6 +47,8 @@ func assertGroupInfo(t *testing.T, gInfo GroupInfo, minIndex, maxIndex int, tota } func TestCheckHeadSizeLimit(t *testing.T) { + t.Parallel() + g := createTestGroupWithHeadSizeLimit(t, 1000*1000) // At first, there are no files. @@ -93,6 +95,8 @@ func TestCheckHeadSizeLimit(t *testing.T) { } func TestRotateFile(t *testing.T) { + t.Parallel() + g := createTestGroupWithHeadSizeLimit(t, 0) g.WriteLine("Line 1") g.WriteLine("Line 2") @@ -123,6 +127,8 @@ func TestRotateFile(t *testing.T) { } func TestWrite(t *testing.T) { + t.Parallel() + g := createTestGroupWithHeadSizeLimit(t, 0) written := []byte("Medusa") @@ -144,6 +150,8 @@ func TestWrite(t *testing.T) { // test that Read reads the required amount of bytes from all the files in the // group and returns no error if n == size of the given slice. func TestGroupReaderRead(t *testing.T) { + t.Parallel() + g := createTestGroupWithHeadSizeLimit(t, 0) professor := []byte("Professor Monster") @@ -173,6 +181,8 @@ func TestGroupReaderRead(t *testing.T) { // test that Read returns an error if number of bytes read < size of // the given slice. Subsequent call should return 0, io.EOF. func TestGroupReaderRead2(t *testing.T) { + t.Parallel() + g := createTestGroupWithHeadSizeLimit(t, 0) professor := []byte("Professor Monster") @@ -204,6 +214,8 @@ func TestGroupReaderRead2(t *testing.T) { } func TestMinIndex(t *testing.T) { + t.Parallel() + g := createTestGroupWithHeadSizeLimit(t, 0) assert.Zero(t, g.MinIndex(), "MinIndex should be zero at the beginning") @@ -213,6 +225,8 @@ func TestMinIndex(t *testing.T) { } func TestMaxIndex(t *testing.T) { + t.Parallel() + g := createTestGroupWithHeadSizeLimit(t, 0) assert.Zero(t, g.MaxIndex(), "MaxIndex should be zero at the beginning") diff --git a/tm2/pkg/bech32/bech32_test.go b/tm2/pkg/bech32/bech32_test.go index 557507cef57..361a18116ab 100644 --- a/tm2/pkg/bech32/bech32_test.go +++ b/tm2/pkg/bech32/bech32_test.go @@ -12,6 +12,8 @@ import ( ) func TestEncodeAndDecode(t *testing.T) { + t.Parallel() + sum := sha256.Sum256([]byte("hello world\n")) bech, err := bech32.ConvertAndEncode("shasum", sum[:]) @@ -39,6 +41,8 @@ var ( ) func TestEncode(t *testing.T) { + t.Parallel() + bz, err := hex.DecodeString(pubkeyBytes) assert.NoError(t, err) @@ -49,6 +53,8 @@ func TestEncode(t *testing.T) { } func TestDecode(t *testing.T) { + t.Parallel() + hrp, b1, err := bech32.Decode(pubkeyBech32) assert.NoError(t, err) diff --git a/tm2/pkg/bft/abci/client/client.go b/tm2/pkg/bft/abci/client/client.go index b3d5a945839..f9f21f08fdb 100644 --- a/tm2/pkg/bft/abci/client/client.go +++ b/tm2/pkg/bft/abci/client/client.go @@ -7,11 +7,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/service" ) -const ( - dialRetryIntervalSeconds = 3 - echoRetryIntervalSeconds = 1 -) - // Client defines an interface for an ABCI client. // All `Async` methods return a `ReqRes` object. // All `Sync` methods return the appropriate protobuf ResponseXxx struct and an error. @@ -48,11 +43,11 @@ type Client interface { EndBlockSync(abci.RequestEndBlock) (abci.ResponseEndBlock, error) } -//---------------------------------------- +// ---------------------------------------- type Callback func(abci.Request, abci.Response) -//---------------------------------------- +// ---------------------------------------- type ReqRes struct { abci.Request diff --git a/tm2/pkg/bft/blockchain/pool_test.go b/tm2/pkg/bft/blockchain/pool_test.go index 79205bff5ce..39c17d8428f 100644 --- a/tm2/pkg/bft/blockchain/pool_test.go +++ b/tm2/pkg/bft/blockchain/pool_test.go @@ -72,6 +72,8 @@ func makePeers(numPeers int, minHeight, maxHeight int64) testPeers { } func TestBlockPoolBasic(t *testing.T) { + t.Parallel() + start := int64(42) peers := makePeers(10, start+1, 1000) errorsCh := make(chan peerError, 1000) @@ -128,6 +130,8 @@ func TestBlockPoolBasic(t *testing.T) { } func TestBlockPoolTimeout(t *testing.T) { + t.Parallel() + start := int64(42) peers := makePeers(10, start+1, 1000) errorsCh := make(chan peerError, 1000) @@ -187,6 +191,8 @@ func TestBlockPoolTimeout(t *testing.T) { } func TestBlockPoolRemovePeer(t *testing.T) { + t.Parallel() + peers := make(testPeers, 10) for i := 0; i < 10; i++ { peerID := p2p.ID(fmt.Sprintf("%d", i+1)) diff --git a/tm2/pkg/bft/blockchain/reactor_test.go b/tm2/pkg/bft/blockchain/reactor_test.go index f4265e0f78d..fef640b0f82 100644 --- a/tm2/pkg/bft/blockchain/reactor_test.go +++ b/tm2/pkg/bft/blockchain/reactor_test.go @@ -115,6 +115,8 @@ func newBlockchainReactor(logger log.Logger, genDoc *types.GenesisDoc, privVals } func TestNoBlockResponse(t *testing.T) { + t.Parallel() + config = cfg.ResetTestRoot("blockchain_reactor_test") defer os.RemoveAll(config.RootDir) genDoc, privVals := randGenesisDoc(1, false, 30) @@ -174,6 +176,8 @@ func TestNoBlockResponse(t *testing.T) { // Alternatively we could actually dial a TCP conn but // that seems extreme. func TestFlappyBadBlockStopsPeer(t *testing.T) { + t.Parallel() + testutils.FilterStability(t, testutils.Flappy) config = cfg.ResetTestRoot("blockchain_reactor_test") @@ -245,6 +249,8 @@ func TestFlappyBadBlockStopsPeer(t *testing.T) { } func TestBcBlockRequestMessageValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { testName string requestHeight int64 @@ -258,6 +264,8 @@ func TestBcBlockRequestMessageValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + request := bcBlockRequestMessage{Height: tc.requestHeight} assert.Equal(t, tc.expectErr, request.ValidateBasic() != nil, "Validate Basic had an unexpected result") }) @@ -265,6 +273,8 @@ func TestBcBlockRequestMessageValidateBasic(t *testing.T) { } func TestBcNoBlockResponseMessageValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { testName string nonResponseHeight int64 @@ -278,6 +288,8 @@ func TestBcNoBlockResponseMessageValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + nonResponse := bcNoBlockResponseMessage{Height: tc.nonResponseHeight} assert.Equal(t, tc.expectErr, nonResponse.ValidateBasic() != nil, "Validate Basic had an unexpected result") }) @@ -285,6 +297,8 @@ func TestBcNoBlockResponseMessageValidateBasic(t *testing.T) { } func TestBcStatusRequestMessageValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { testName string requestHeight int64 @@ -298,6 +312,8 @@ func TestBcStatusRequestMessageValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + request := bcStatusRequestMessage{Height: tc.requestHeight} assert.Equal(t, tc.expectErr, request.ValidateBasic() != nil, "Validate Basic had an unexpected result") }) @@ -305,6 +321,8 @@ func TestBcStatusRequestMessageValidateBasic(t *testing.T) { } func TestBcStatusResponseMessageValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { testName string responseHeight int64 @@ -318,13 +336,15 @@ func TestBcStatusResponseMessageValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + response := bcStatusResponseMessage{Height: tc.responseHeight} assert.Equal(t, tc.expectErr, response.ValidateBasic() != nil, "Validate Basic had an unexpected result") }) } } -//---------------------------------------------- +// ---------------------------------------------- // utility funcs func makeTxs(height int64) (txs []types.Tx) { diff --git a/tm2/pkg/bft/config/config.go b/tm2/pkg/bft/config/config.go index 6f148c3b5c1..3785759c960 100644 --- a/tm2/pkg/bft/config/config.go +++ b/tm2/pkg/bft/config/config.go @@ -9,6 +9,7 @@ import ( cns "github.com/gnolang/gno/tm2/pkg/bft/consensus/config" mem "github.com/gnolang/gno/tm2/pkg/bft/mempool/config" rpc "github.com/gnolang/gno/tm2/pkg/bft/rpc/config" + eventstore "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" "github.com/gnolang/gno/tm2/pkg/errors" osm "github.com/gnolang/gno/tm2/pkg/os" p2p "github.com/gnolang/gno/tm2/pkg/p2p/config" @@ -20,59 +21,70 @@ type Config struct { BaseConfig `toml:",squash"` // Options for services - RPC *rpc.RPCConfig `toml:"rpc"` - P2P *p2p.P2PConfig `toml:"p2p"` - Mempool *mem.MempoolConfig `toml:"mempool"` - Consensus *cns.ConsensusConfig `toml:"consensus"` + RPC *rpc.RPCConfig `toml:"rpc"` + P2P *p2p.P2PConfig `toml:"p2p"` + Mempool *mem.MempoolConfig `toml:"mempool"` + Consensus *cns.ConsensusConfig `toml:"consensus"` + TxEventStore *eventstore.Config `toml:"tx_event_store"` } // DefaultConfig returns a default configuration for a Tendermint node func DefaultConfig() *Config { return &Config{ - BaseConfig: DefaultBaseConfig(), - RPC: rpc.DefaultRPCConfig(), - P2P: p2p.DefaultP2PConfig(), - Mempool: mem.DefaultMempoolConfig(), - Consensus: cns.DefaultConsensusConfig(), + BaseConfig: DefaultBaseConfig(), + RPC: rpc.DefaultRPCConfig(), + P2P: p2p.DefaultP2PConfig(), + Mempool: mem.DefaultMempoolConfig(), + Consensus: cns.DefaultConsensusConfig(), + TxEventStore: eventstore.DefaultEventStoreConfig(), } } -// Like LoadOrMakeConfigWithOptions() but without overriding any defaults. -func LoadOrMakeDefaultConfig(root string) (cfg *Config) { - return LoadOrMakeConfigWithOptions(root, nil) -} - type ConfigOptions func(cfg *Config) -// LoadOrMakeConfigWithOptions() loads configuration or saves one +// LoadOrMakeConfigWithOptions loads configuration or saves one // made by modifying the default config with override options -func LoadOrMakeConfigWithOptions(root string, options ConfigOptions) (cfg *Config) { +func LoadOrMakeConfigWithOptions(root string, options ConfigOptions) (*Config, error) { + var cfg *Config + configPath := join(root, defaultConfigFilePath) if osm.FileExists(configPath) { - cfg = LoadConfigFile(configPath) + var loadErr error + + // Load the configuration + if cfg, loadErr = LoadConfigFile(configPath); loadErr != nil { + return nil, loadErr + } + cfg.SetRootDir(root) cfg.EnsureDirs() } else { cfg = DefaultConfig() - options(cfg) + if options != nil { + options(cfg) + } cfg.SetRootDir(root) cfg.EnsureDirs() WriteConfigFile(configPath, cfg) + + // Validate the configuration + if validateErr := cfg.ValidateBasic(); validateErr != nil { + return nil, fmt.Errorf("unable to validate config, %w", validateErr) + } } - if err := cfg.ValidateBasic(); err != nil { - panic(err) - } - return cfg + + return cfg, nil } // TestConfig returns a configuration that can be used for testing func TestConfig() *Config { return &Config{ - BaseConfig: TestBaseConfig(), - RPC: rpc.TestRPCConfig(), - P2P: p2p.TestP2PConfig(), - Mempool: mem.TestMempoolConfig(), - Consensus: cns.TestConsensusConfig(), + BaseConfig: TestBaseConfig(), + RPC: rpc.TestRPCConfig(), + P2P: p2p.TestP2PConfig(), + Mempool: mem.TestMempoolConfig(), + Consensus: cns.TestConsensusConfig(), + TxEventStore: eventstore.DefaultEventStoreConfig(), } } @@ -121,7 +133,7 @@ func (cfg *Config) ValidateBasic() error { return nil } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // BaseConfig const ( diff --git a/tm2/pkg/bft/config/toml.go b/tm2/pkg/bft/config/toml.go index a35e5674631..1599bc78968 100644 --- a/tm2/pkg/bft/config/toml.go +++ b/tm2/pkg/bft/config/toml.go @@ -3,7 +3,6 @@ package config import ( "bytes" "fmt" - "io/ioutil" "os" "path/filepath" "text/template" @@ -24,17 +23,27 @@ func init() { } } -func LoadConfigFile(configFilePath string) *Config { - bz, err := os.ReadFile(configFilePath) - if err != nil { - panic(err) +// LoadConfigFile loads the TOML node configuration from the specified path +func LoadConfigFile(path string) (*Config, error) { + // Read the config file + content, readErr := os.ReadFile(path) + if readErr != nil { + return nil, readErr } - var config Config - err = toml.Unmarshal(bz, &config) - if err != nil { - panic(err) + + // Parse the node config + var nodeConfig Config + + if unmarshalErr := toml.Unmarshal(content, &nodeConfig); unmarshalErr != nil { + return nil, unmarshalErr + } + + // Validate the config + if validateErr := nodeConfig.ValidateBasic(); validateErr != nil { + return nil, fmt.Errorf("unable to validate config, %w", validateErr) } - return &config + + return &nodeConfig, nil } /****** these are for production settings ***********/ @@ -298,7 +307,7 @@ func ResetTestRoot(testName string) *Config { func ResetTestRootWithChainID(testName string, chainID string) *Config { // create a unique, concurrency-safe test directory under os.TempDir() - rootDir, err := ioutil.TempDir("", fmt.Sprintf("%s-%s_", chainID, testName)) + rootDir, err := os.MkdirTemp("", fmt.Sprintf("%s-%s_", chainID, testName)) if err != nil { panic(err) } diff --git a/tm2/pkg/bft/config/toml_test.go b/tm2/pkg/bft/config/toml_test.go index 0fe78285997..d1d70155cb8 100644 --- a/tm2/pkg/bft/config/toml_test.go +++ b/tm2/pkg/bft/config/toml_test.go @@ -5,6 +5,8 @@ import ( "strings" "testing" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/pelletier/go-toml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -20,6 +22,8 @@ func ensureFiles(t *testing.T, rootDir string, files ...string) { } func TestEnsureRoot(t *testing.T) { + t.Parallel() + require := require.New(t) // setup temp dir for test @@ -44,6 +48,8 @@ func TestEnsureRoot(t *testing.T) { } func TestEnsureTestRoot(t *testing.T) { + t.Parallel() + require := require.New(t) testName := "ensureTestRoot" @@ -95,3 +101,63 @@ func checkConfig(configFile string) bool { } return valid } + +func TestTOML_LoadConfig(t *testing.T) { + t.Parallel() + + t.Run("config does not exist", func(t *testing.T) { + t.Parallel() + + cfg, loadErr := LoadConfigFile("dummy-path") + + assert.Error(t, loadErr) + assert.Nil(t, cfg) + }) + + t.Run("config is not valid toml", func(t *testing.T) { + t.Parallel() + + // Create config file + configFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + // Write invalid TOML + _, writeErr := configFile.WriteString("invalid TOML") + require.NoError(t, writeErr) + + cfg, loadErr := LoadConfigFile(configFile.Name()) + + assert.Error(t, loadErr) + assert.Nil(t, cfg) + }) + + t.Run("valid config", func(t *testing.T) { + t.Parallel() + + // Create config file + configFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + // Create the default config + defaultConfig := DefaultConfig() + + // Marshal the default config + defaultConfigRaw, marshalErr := toml.Marshal(defaultConfig) + require.NoError(t, marshalErr) + + // Write valid TOML + _, writeErr := configFile.Write(defaultConfigRaw) + require.NoError(t, writeErr) + + cfg, loadErr := LoadConfigFile(configFile.Name()) + require.NoError(t, loadErr) + + assert.EqualValues(t, defaultConfig.BaseConfig, cfg.BaseConfig) + assert.EqualValues(t, defaultConfig.RPC, cfg.RPC) + assert.EqualValues(t, defaultConfig.P2P, cfg.P2P) + assert.EqualValues(t, defaultConfig.Mempool, cfg.Mempool) + assert.EqualValues(t, defaultConfig.Consensus, cfg.Consensus) + assert.Equal(t, defaultConfig.TxEventStore.EventStoreType, cfg.TxEventStore.EventStoreType) + assert.Empty(t, defaultConfig.TxEventStore.Params, cfg.TxEventStore.Params) + }) +} diff --git a/tm2/pkg/bft/consensus/common_test.go b/tm2/pkg/bft/consensus/common_test.go index 7424305c00a..ba19881aace 100644 --- a/tm2/pkg/bft/consensus/common_test.go +++ b/tm2/pkg/bft/consensus/common_test.go @@ -3,7 +3,6 @@ package consensus import ( "bytes" "fmt" - "io/ioutil" "os" "path" "path/filepath" @@ -30,7 +29,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/p2p" ) const ( @@ -662,11 +660,11 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF if i < nValidators { privVal = privVals[i] } else { - tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") + tempKeyFile, err := os.CreateTemp("", "priv_validator_key_") if err != nil { panic(err) } - tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + tempStateFile, err := os.CreateTemp("", "priv_validator_state_") if err != nil { panic(err) } @@ -702,15 +700,6 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF } } -func getSwitchIndex(switches []*p2p.Switch, peer p2p.Peer) int { - for i, s := range switches { - if peer.NodeInfo().ID() == s.NodeInfo().ID() { - return i - } - } - panic("didnt find peer in switches") -} - // ------------------------------------------------------------------------------- // genesis @@ -795,7 +784,7 @@ func newCounter() abci.Application { } func newPersistentKVStore() abci.Application { - dir, err := ioutil.TempDir("", "persistent-kvstore") + dir, err := os.MkdirTemp("", "persistent-kvstore") if err != nil { panic(err) } diff --git a/tm2/pkg/bft/consensus/mempool_test.go b/tm2/pkg/bft/consensus/mempool_test.go index b9c04cabbed..85914262903 100644 --- a/tm2/pkg/bft/consensus/mempool_test.go +++ b/tm2/pkg/bft/consensus/mempool_test.go @@ -24,6 +24,8 @@ func assertMempool(txn txNotifier) mempl.Mempool { } func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) { + t.Parallel() + config := ResetConfig("consensus_mempool_no_progress_until_txs_available") defer os.RemoveAll(config.RootDir) config.Consensus.CreateEmptyBlocks = false @@ -71,6 +73,8 @@ func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) { } func TestMempoolProgressInHigherRound(t *testing.T) { + t.Parallel() + config := ResetConfig("consensus_mempool_progress_in_higher_round") defer os.RemoveAll(config.RootDir) config.Consensus.CreateEmptyBlocks = false @@ -138,6 +142,8 @@ func deliverTxsRange(cs *ConsensusState, start, end int) { } func TestMempoolTxConcurrentWithCommit(t *testing.T) { + t.Parallel() + state, privVals := randGenesisState(1, false, 10) blockDB := dbm.NewMemDB() app := NewCounterApplication() @@ -169,6 +175,8 @@ func TestMempoolTxConcurrentWithCommit(t *testing.T) { } func TestMempoolRmBadTx(t *testing.T) { + t.Parallel() + state, privVals := randGenesisState(1, false, 10) app := NewCounterApplication() blockDB := dbm.NewMemDB() diff --git a/tm2/pkg/bft/consensus/reactor_test.go b/tm2/pkg/bft/consensus/reactor_test.go index 2194d4d007d..db084b61395 100644 --- a/tm2/pkg/bft/consensus/reactor_test.go +++ b/tm2/pkg/bft/consensus/reactor_test.go @@ -86,6 +86,8 @@ func stopConsensusNet(logger log.Logger, reactors []*ConsensusReactor, eventSwit // Ensure a testnet makes blocks func TestReactorBasic(t *testing.T) { + t.Parallel() + N := 4 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() @@ -101,6 +103,8 @@ func TestReactorBasic(t *testing.T) { // Ensure a testnet makes blocks when there are txs func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { + t.Parallel() + N := 4 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter, func(c *cfg.Config) { @@ -122,6 +126,8 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { } func TestReactorReceiveDoesNotPanicIfAddPeerHasntBeenCalledYet(t *testing.T) { + t.Parallel() + N := 1 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() @@ -144,6 +150,8 @@ func TestReactorReceiveDoesNotPanicIfAddPeerHasntBeenCalledYet(t *testing.T) { } func TestReactorReceivePanicsIfInitPeerHasntBeenCalledYet(t *testing.T) { + t.Parallel() + N := 1 css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) defer cleanup() @@ -166,6 +174,8 @@ func TestReactorReceivePanicsIfInitPeerHasntBeenCalledYet(t *testing.T) { // Test we record stats about votes and block parts from other peers. func TestFlappyReactorRecordsVotesAndBlockParts(t *testing.T) { + t.Parallel() + testutils.FilterStability(t, testutils.Flappy) N := 4 @@ -192,6 +202,8 @@ func TestFlappyReactorRecordsVotesAndBlockParts(t *testing.T) { // ensure we can make blocks despite cycling a validator set func TestReactorVotingPowerChange(t *testing.T) { + t.Parallel() + nVals := 4 logger := log.TestingLogger() css, cleanup := randConsensusNet(nVals, "consensus_voting_power_changes_test", newMockTickerFunc(true), newPersistentKVStore) @@ -254,6 +266,8 @@ func TestReactorVotingPowerChange(t *testing.T) { } func TestReactorValidatorSetChanges(t *testing.T) { + t.Parallel() + nPeers := 7 nVals := 4 css, _, _, cleanup := randConsensusNetWithPeers(nVals, nPeers, "consensus_val_set_changes_test", newMockTickerFunc(true), newPersistentKVStoreWithPath) @@ -350,6 +364,8 @@ func TestReactorValidatorSetChanges(t *testing.T) { // Check we can make blocks with skip_timeout_commit=false func TestReactorWithTimeoutCommit(t *testing.T) { + t.Parallel() + N := 4 css, cleanup := randConsensusNet(N, "consensus_reactor_with_timeout_commit_test", newMockTickerFunc(false), newCounter) defer cleanup() @@ -510,6 +526,8 @@ func timeoutWaitGroup(t *testing.T, n int, f func(int), css []*ConsensusState) { // Ensure basic validation of structs is functioning func TestNewRoundStepMessageValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { testName string messageHeight int64 @@ -529,6 +547,8 @@ func TestNewRoundStepMessageValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + message := NewRoundStepMessage{ Height: tc.messageHeight, Round: tc.messageRound, @@ -542,6 +562,8 @@ func TestNewRoundStepMessageValidateBasic(t *testing.T) { } func TestNewValidBlockMessageValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { malleateFn func(*NewValidBlockMessage) expErr string @@ -569,6 +591,8 @@ func TestNewValidBlockMessageValidateBasic(t *testing.T) { for i, tc := range testCases { tc := tc t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + t.Parallel() + msg := &NewValidBlockMessage{ Height: 1, Round: 0, @@ -588,6 +612,8 @@ func TestNewValidBlockMessageValidateBasic(t *testing.T) { } func TestProposalPOLMessageValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { malleateFn func(*ProposalPOLMessage) expErr string @@ -605,6 +631,8 @@ func TestProposalPOLMessageValidateBasic(t *testing.T) { for i, tc := range testCases { tc := tc t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + t.Parallel() + msg := &ProposalPOLMessage{ Height: 1, ProposalPOLRound: 1, @@ -621,6 +649,8 @@ func TestProposalPOLMessageValidateBasic(t *testing.T) { } func TestBlockPartMessageValidateBasic(t *testing.T) { + t.Parallel() + testPart := new(types.Part) testPart.Proof.LeafHash = tmhash.Sum([]byte("leaf")) testCases := []struct { @@ -638,6 +668,8 @@ func TestBlockPartMessageValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + message := BlockPartMessage{ Height: tc.messageHeight, Round: tc.messageRound, @@ -655,6 +687,8 @@ func TestBlockPartMessageValidateBasic(t *testing.T) { } func TestHasVoteMessageValidateBasic(t *testing.T) { + t.Parallel() + const ( validSignedMsgType types.SignedMsgType = 0x01 invalidSignedMsgType types.SignedMsgType = 0x03 @@ -678,6 +712,8 @@ func TestHasVoteMessageValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + message := HasVoteMessage{ Height: tc.messageHeight, Round: tc.messageRound, @@ -691,6 +727,8 @@ func TestHasVoteMessageValidateBasic(t *testing.T) { } func TestVoteSetMaj23MessageValidateBasic(t *testing.T) { + t.Parallel() + const ( validSignedMsgType types.SignedMsgType = 0x01 invalidSignedMsgType types.SignedMsgType = 0x03 @@ -723,6 +761,8 @@ func TestVoteSetMaj23MessageValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + message := VoteSetMaj23Message{ Height: tc.messageHeight, Round: tc.messageRound, @@ -736,6 +776,8 @@ func TestVoteSetMaj23MessageValidateBasic(t *testing.T) { } func TestVoteSetBitsMessageValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { //nolint: maligned malleateFn func(*VoteSetBitsMessage) expErr string @@ -762,6 +804,8 @@ func TestVoteSetBitsMessageValidateBasic(t *testing.T) { for i, tc := range testCases { tc := tc t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + t.Parallel() + msg := &VoteSetBitsMessage{ Height: 1, Round: 0, diff --git a/tm2/pkg/bft/consensus/replay.go b/tm2/pkg/bft/consensus/replay.go index 16b4ba3fa87..b228c1b63e4 100644 --- a/tm2/pkg/bft/consensus/replay.go +++ b/tm2/pkg/bft/consensus/replay.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "fmt" - "hash/crc32" "io" "reflect" "time" @@ -21,8 +20,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/log" ) -var crc32c = crc32.MakeTable(crc32.Castagnoli) - // Functionality to replay blocks and messages on recovery from a crash. // There are two general failure scenarios: // diff --git a/tm2/pkg/bft/consensus/replay_test.go b/tm2/pkg/bft/consensus/replay_test.go index 3174207ef8d..d43a06f40c2 100644 --- a/tm2/pkg/bft/consensus/replay_test.go +++ b/tm2/pkg/bft/consensus/replay_test.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "path/filepath" "runtime" @@ -124,6 +123,8 @@ func sendTxs(ctx context.Context, cs *ConsensusState) { // TestWALCrash uses crashing WAL to test we can recover from any WAL failure. func TestWALCrash(t *testing.T) { + t.Parallel() + testCases := []struct { name string initFn func(dbm.DB, *ConsensusState, context.Context) @@ -147,6 +148,8 @@ func TestWALCrash(t *testing.T) { tc := tc consensusReplayConfig := ResetConfig(fmt.Sprintf("%s_%d", t.Name(), i)) t.Run(tc.name, func(t *testing.T) { + t.Parallel() + crashWALandCheckLiveness(t, consensusReplayConfig, tc.initFn, tc.lastBlockHeight) }) } @@ -507,6 +510,8 @@ func makeTestSim(t *testing.T, name string) (sim testSim) { // Sync from scratch func TestHandshakeReplayAll(t *testing.T) { + t.Parallel() + for _, m := range modes { testHandshakeReplay(t, config, 0, m, nil) } @@ -519,6 +524,8 @@ func TestHandshakeReplayAll(t *testing.T) { // Sync many, not from scratch func TestHandshakeReplaySome(t *testing.T) { + t.Parallel() + for _, m := range modes { testHandshakeReplay(t, config, 1, m, nil) } @@ -531,6 +538,8 @@ func TestHandshakeReplaySome(t *testing.T) { // Sync from lagging by one func TestHandshakeReplayOne(t *testing.T) { + t.Parallel() + for _, m := range modes { testHandshakeReplay(t, config, numBlocks-1, m, nil) } @@ -543,6 +552,8 @@ func TestHandshakeReplayOne(t *testing.T) { // Sync from caught up func TestFlappyHandshakeReplayNone(t *testing.T) { + t.Parallel() + testutils.FilterStability(t, testutils.Flappy) for _, m := range modes { @@ -557,6 +568,8 @@ func TestFlappyHandshakeReplayNone(t *testing.T) { // Test mockProxyApp should not panic when app return ABCIResponses with some empty ResponseDeliverTx func TestMockProxyApp(t *testing.T) { + t.Parallel() + logger := log.TestingLogger() validTxs, invalidTxs := 0, 0 txIndex := 0 @@ -604,7 +617,7 @@ func TestMockProxyApp(t *testing.T) { } func tempWALWithData(data []byte) string { - walFile, err := ioutil.TempFile("", "wal") + walFile, err := os.CreateTemp("", "wal") if err != nil { panic(fmt.Sprintf("failed to create temp WAL file: %v", err)) } @@ -811,6 +824,8 @@ func buildTMStateFromChain(config *cfg.Config, stateDB dbm.DB, state sm.State, c } func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) { + t.Parallel() + // 1. Initialize tendermint and commit 3 blocks with the following app hashes: // - 0x01 // - 0x02 @@ -1090,6 +1105,8 @@ func (bs *mockBlockStore) LoadSeenCommit(height int64) *types.Commit { // Test handshake/init chain func TestHandshakeUpdatesValidators(t *testing.T) { + t.Parallel() + val, _ := types.RandValidator(true, 10) vals := types.NewValidatorSet([]*types.Validator{val}) app := &initChainApp{vals: vals.ABCIValidatorUpdates()} diff --git a/tm2/pkg/bft/consensus/state_test.go b/tm2/pkg/bft/consensus/state_test.go index 1587fa03057..35877837ab3 100644 --- a/tm2/pkg/bft/consensus/state_test.go +++ b/tm2/pkg/bft/consensus/state_test.go @@ -59,10 +59,12 @@ x * TestHalt1 - if we see +2/3 precommits after timing out into new round, we sh */ -//---------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- // ProposeSuite func TestStateProposerSelection0(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) height, round := cs1.Height, cs1.Round @@ -103,6 +105,8 @@ func TestStateProposerSelection0(t *testing.T) { // Now let's do it all again, but starting from round 2 instead of 0 func TestStateProposerSelection2(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) // test needs more work for more than 3 validators height := cs1.Height newRoundCh := subscribe(cs1.evsw, cstypes.EventNewRound{}) @@ -138,6 +142,8 @@ func TestStateProposerSelection2(t *testing.T) { // a non-validator should timeout into the prevote round func TestStateEnterProposeNoPrivValidator(t *testing.T) { + t.Parallel() + cs, _ := randConsensusState(1) cs.SetPrivValidator(nil) height, round := cs.Height, cs.Round @@ -161,6 +167,8 @@ func TestStateEnterProposeNoPrivValidator(t *testing.T) { // a validator should not timeout of the prevote round (TODO: unless the block is really big!) func TestStateEnterProposeYesPrivValidator(t *testing.T) { + t.Parallel() + cs, _ := randConsensusState(1) height, round := cs.Height, cs.Round @@ -197,6 +205,8 @@ func TestStateEnterProposeYesPrivValidator(t *testing.T) { } func TestStateBadProposal(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(2) height, round := cs1.Height, cs1.Round vs2 := vss[1] @@ -255,11 +265,13 @@ func TestStateBadProposal(t *testing.T) { signAddVotes(cs1, types.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) } -//---------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- // FullRoundSuite // propose, prevote, and precommit a block func TestStateFullRound1(t *testing.T) { + t.Parallel() + cs, vss := randConsensusState(1) height, round := cs.Height, cs.Round @@ -292,6 +304,8 @@ func TestStateFullRound1(t *testing.T) { // nil is proposed, so prevote and precommit nil func TestStateFullRoundNil(t *testing.T) { + t.Parallel() + cs, vss := randConsensusState(1) height, round := cs.Height, cs.Round cs.decideProposal = func(height int64, round int) { @@ -316,6 +330,8 @@ func TestStateFullRoundNil(t *testing.T) { // run through propose, prevote, precommit commit with two validators // where the first validator has to wait for votes from the second func TestStateFullRound2(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(2) vs2 := vss[1] height, round := cs1.Height, cs1.Round @@ -354,12 +370,14 @@ func TestStateFullRound2(t *testing.T) { ensureNewBlock(newBlockCh, height) } -//------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------ // LockSuite // two validators, 4 rounds. // two vals take turns proposing. val1 locks on first one, precommits nil on everything else func TestStateLockNoPOL(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(2) vs2 := vss[1] height, round := cs1.Height, cs1.Round @@ -409,7 +427,7 @@ func TestStateLockNoPOL(t *testing.T) { // but with invalid args. then we enterPrecommitWait, and the timeout to new round ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) - /// + // ----------- round++ // moving to the next round ensureNewRound(newRoundCh, height, round) @@ -532,6 +550,8 @@ func TestStateLockNoPOL(t *testing.T) { // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka func TestStateLockPOLRelock(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -627,6 +647,8 @@ func TestStateLockPOLRelock(t *testing.T) { // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka func TestStateLockPOLUnlock(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -727,6 +749,8 @@ func TestStateLockPOLUnlock(t *testing.T) { // then a polka at round 2 that we lock on // then we see the polka from round 1 but shouldn't unlock func TestStateLockPOLSafety1(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -848,6 +872,8 @@ func TestStateLockPOLSafety1(t *testing.T) { // What we want: // dont see P0, lock on P1 at R1, dont unlock using P0 at R2 func TestStateLockPOLSafety2(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -943,6 +969,8 @@ func TestStateLockPOLSafety2(t *testing.T) { // What we want: // P0 proposes B0 at R3. func TestProposeValidBlock(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1038,6 +1066,8 @@ func TestProposeValidBlock(t *testing.T) { // What we want: // P0 miss to lock B but set valid block to B after receiving delayed prevote. func TestSetValidBlockOnDelayedPrevote(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1102,6 +1132,8 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { // P0 miss to lock B as Proposal Block is missing, but set valid block to B after // receiving delayed Block Proposal. func TestSetValidBlockOnDelayedProposal(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1160,6 +1192,8 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) { // What we want: // P0 waits for timeoutPrecommit before starting next round func TestWaitingTimeoutOnNilPolka(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1185,6 +1219,8 @@ func TestWaitingTimeoutOnNilPolka(t *testing.T) { // What we want: // P0 waits for timeoutPropose in the next round before entering prevote func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1223,6 +1259,8 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { // What we want: // P0 jump to higher round, precommit and start precommit wait func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1261,6 +1299,8 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { // What we want: // P0 wait for timeoutPropose to expire before sending prevote. func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, 1 @@ -1290,6 +1330,8 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { // What we want: // P0 emit NewValidBlock event upon receiving 2/3+ Precommit for B but hasn't received block B yet func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, 1 @@ -1327,6 +1369,8 @@ func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) { // P0 receives 2/3+ Precommit for B for round 0, while being in round 1. It emits NewValidBlock event. // After receiving block, it executes block and moves to the next height. func TestCommitFromPreviousRound(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, 1 @@ -1439,6 +1483,8 @@ func TestStartNextHeightCorrectly(t *testing.T) { } func TestFlappyResetTimeoutPrecommitUponNewHeight(t *testing.T) { + t.Parallel() + testutils.FilterStability(t, testutils.Flappy) config.Consensus.SkipTimeoutCommit = false @@ -1496,12 +1542,14 @@ func TestFlappyResetTimeoutPrecommitUponNewHeight(t *testing.T) { assert.False(t, rs.TriggeredTimeoutPrecommit, "triggeredTimeoutPrecommit should be false at the beginning of each height") } -//------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------ // SlashingSuite // TODO: Slashing /* func TestStateSlashingPrevotes(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(2) vs2 := vss[1] @@ -1537,6 +1585,8 @@ func TestStateSlashingPrevotes(t *testing.T) { } func TestStateSlashingPrecommits(t *testing.T) { + t.Parallel() + cs1, vss := randConsensusState(2) vs2 := vss[1] @@ -1573,15 +1623,17 @@ func TestStateSlashingPrecommits(t *testing.T) { } */ -//------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------ // CatchupSuite -//------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------ // HaltSuite // 4 vals. // we receive a final precommit after going into next round, but others might have gone to commit already! func TestFlappyStateHalt1(t *testing.T) { + t.Parallel() + testutils.FilterStability(t, testutils.Flappy) cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] @@ -1652,6 +1704,8 @@ func TestFlappyStateHalt1(t *testing.T) { } func TestStateOutputsBlockPartsStats(t *testing.T) { + t.Parallel() + // create dummy peer cs, _ := randConsensusState(1) peer := p2pmock.NewPeer(nil) @@ -1694,6 +1748,8 @@ func TestStateOutputsBlockPartsStats(t *testing.T) { } func TestStateOutputVoteStats(t *testing.T) { + t.Parallel() + cs, vss := randConsensusState(2) // create dummy peer peer := p2pmock.NewPeer(nil) diff --git a/tm2/pkg/bft/consensus/ticker.go b/tm2/pkg/bft/consensus/ticker.go index c4f660aae16..f1a33dd9c38 100644 --- a/tm2/pkg/bft/consensus/ticker.go +++ b/tm2/pkg/bft/consensus/ticker.go @@ -86,7 +86,7 @@ func (t *timeoutTicker) stopTimer() { } // send on tickChan to start a new timer. -// timers are interupted and replaced by new ticks from later steps +// timers are interrupted and replaced by new ticks from later steps // timeouts of 0 on the tickChan will be immediately relayed to the tockChan func (t *timeoutTicker) timeoutRoutine() { t.Logger.Debug("Starting timeout routine") diff --git a/tm2/pkg/bft/consensus/types/height_vote_set_test.go b/tm2/pkg/bft/consensus/types/height_vote_set_test.go index 86dc4ee84f6..4c315585daa 100644 --- a/tm2/pkg/bft/consensus/types/height_vote_set_test.go +++ b/tm2/pkg/bft/consensus/types/height_vote_set_test.go @@ -21,6 +21,8 @@ func TestMain(m *testing.M) { } func TestPeerCatchupRounds(t *testing.T) { + t.Parallel() + valSet, privVals := types.RandValidatorSet(10, 1) hvs := NewHeightVoteSet(config.ChainID(), 1, valSet) diff --git a/tm2/pkg/bft/consensus/wal_generator.go b/tm2/pkg/bft/consensus/wal_generator.go index dff1cca1446..7893c544c7f 100644 --- a/tm2/pkg/bft/consensus/wal_generator.go +++ b/tm2/pkg/bft/consensus/wal_generator.go @@ -40,7 +40,7 @@ func WALGenerateNBlocks(t *testing.T, wr io.Writer, numBlocks int) (err error) { logger := log.TestingLogger().With("wal_generator", "wal_generator") logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks) - ///////////////////////////////////////////////////////////////////////////// + // ----------- // COPY PASTE FROM node.go WITH A FEW MODIFICATIONS // NOTE: we can't import node package because of circular dependency. // NOTE: we don't do handshake so need to set state.Version.Consensus.App directly. @@ -83,7 +83,7 @@ func WALGenerateNBlocks(t *testing.T, wr io.Writer, numBlocks int) (err error) { consensusState.SetPrivValidator(privValidator) } // END OF COPY PASTE - ///////////////////////////////////////////////////////////////////////////// + // ----------- // set consensus wal to buffered WAL, which will write all incoming msgs to buffer numBlocksWritten := make(chan struct{}) diff --git a/tm2/pkg/bft/consensus/wal_test.go b/tm2/pkg/bft/consensus/wal_test.go index 1b271fb73c7..55c9be03508 100644 --- a/tm2/pkg/bft/consensus/wal_test.go +++ b/tm2/pkg/bft/consensus/wal_test.go @@ -13,10 +13,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/log" ) -const ( - walTestFlushInterval = time.Duration(100) * time.Millisecond -) - // ---------------------------------------- // copied over from wal/wal_test.go @@ -53,8 +49,9 @@ func makeTempWAL(t *testing.T, walChunkSize int64) (wal walm.WAL) { // ---------------------------------------- func TestWALTruncate(t *testing.T) { - const maxTestMsgSize = 1024 * 1024 // 1MB - const walChunkSize = 409610 // 4KB + t.Parallel() + + const walChunkSize = 409610 // 4KB wal := makeTempWAL(t, walChunkSize) wal.SetLogger(log.TestingLogger()) diff --git a/tm2/pkg/bft/mempool/cache_test.go b/tm2/pkg/bft/mempool/cache_test.go index 5f76d6875ea..2d523db37ae 100644 --- a/tm2/pkg/bft/mempool/cache_test.go +++ b/tm2/pkg/bft/mempool/cache_test.go @@ -13,6 +13,8 @@ import ( ) func TestCacheRemove(t *testing.T) { + t.Parallel() + cache := newMapTxCache(100) numTxs := 10 txs := make([][]byte, numTxs) @@ -35,6 +37,8 @@ func TestCacheRemove(t *testing.T) { } func TestCacheAfterUpdate(t *testing.T) { + t.Parallel() + app := kvstore.NewKVStoreApplication() cc := proxy.NewLocalClientCreator(app) mempool, cleanup := newMempoolWithApp(cc) diff --git a/tm2/pkg/bft/mempool/reactor_test.go b/tm2/pkg/bft/mempool/reactor_test.go index c3b77fb46df..154cb1a7132 100644 --- a/tm2/pkg/bft/mempool/reactor_test.go +++ b/tm2/pkg/bft/mempool/reactor_test.go @@ -140,6 +140,8 @@ const ( ) func TestReactorBroadcastTxMessage(t *testing.T) { + t.Parallel() + mconfig := memcfg.TestMempoolConfig() pconfig := p2pcfg.TestP2PConfig() const N = 4 @@ -162,6 +164,8 @@ func TestReactorBroadcastTxMessage(t *testing.T) { } func TestReactorNoBroadcastToSender(t *testing.T) { + t.Parallel() + mconfig := memcfg.TestMempoolConfig() pconfig := p2pcfg.TestP2PConfig() const N = 2 @@ -179,6 +183,8 @@ func TestReactorNoBroadcastToSender(t *testing.T) { } func TestFlappyBroadcastTxForPeerStopsWhenPeerStops(t *testing.T) { + t.Parallel() + testutils.FilterStability(t, testutils.Flappy) if testing.Short() { @@ -205,6 +211,8 @@ func TestFlappyBroadcastTxForPeerStopsWhenPeerStops(t *testing.T) { } func TestFlappyBroadcastTxForPeerStopsWhenReactorStops(t *testing.T) { + t.Parallel() + testutils.FilterStability(t, testutils.Flappy) if testing.Short() { @@ -227,6 +235,8 @@ func TestFlappyBroadcastTxForPeerStopsWhenReactorStops(t *testing.T) { } func TestMempoolIDsBasic(t *testing.T) { + t.Parallel() + ids := newMempoolIDs() peer := mock.NewPeer(net.IP{127, 0, 0, 1}) @@ -241,6 +251,8 @@ func TestMempoolIDsBasic(t *testing.T) { } func TestMempoolIDsPanicsIfNodeRequestsOvermaxActiveIDs(t *testing.T) { + t.Parallel() + if testing.Short() { return } diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 23b42cec6b9..9d6143e7af3 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" "github.com/rs/cors" "github.com/gnolang/gno/tm2/pkg/amino" @@ -25,8 +26,8 @@ import ( _ "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" rpcserver "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/server" sm "github.com/gnolang/gno/tm2/pkg/bft/state" - "github.com/gnolang/gno/tm2/pkg/bft/state/txindex" - "github.com/gnolang/gno/tm2/pkg/bft/state/txindex/null" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/null" "github.com/gnolang/gno/tm2/pkg/bft/store" "github.com/gnolang/gno/tm2/pkg/bft/types" tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time" @@ -154,18 +155,18 @@ type Node struct { isListening bool // services - evsw events.EventSwitch - stateDB dbm.DB - blockStore *store.BlockStore // store the blockchain to disk - bcReactor p2p.Reactor // for fast-syncing - mempoolReactor *mempl.Reactor // for gossipping transactions - mempool mempl.Mempool - consensusState *cs.ConsensusState // latest consensus state - consensusReactor *cs.ConsensusReactor // for participating in the consensus - proxyApp proxy.AppConns // connection to the application - rpcListeners []net.Listener // rpc servers - txIndexer txindex.TxIndexer - indexerService *txindex.IndexerService + evsw events.EventSwitch + stateDB dbm.DB + blockStore *store.BlockStore // store the blockchain to disk + bcReactor p2p.Reactor // for fast-syncing + mempoolReactor *mempl.Reactor // for gossipping transactions + mempool mempl.Mempool + consensusState *cs.ConsensusState // latest consensus state + consensusReactor *cs.ConsensusReactor // for participating in the consensus + proxyApp proxy.AppConns // connection to the application + rpcListeners []net.Listener // rpc servers + txEventStore eventstore.TxEventStore + eventStoreService *eventstore.Service } func initDBs(config *cfg.Config, dbProvider DBProvider) (blockStore *store.BlockStore, stateDB dbm.DB, err error) { @@ -193,36 +194,36 @@ func createAndStartProxyAppConns(clientCreator proxy.ClientCreator, logger log.L return proxyApp, nil } -func createAndStartIndexerService(config *cfg.Config, dbProvider DBProvider, - evsw events.EventSwitch, logger log.Logger, -) (*txindex.IndexerService, txindex.TxIndexer, error) { - var txIndexer txindex.TxIndexer = &null.TxIndex{} - /* - switch config.TxIndex.Indexer { - case "kv": - store, err := dbProvider(&DBContext{"tx_index", config}) - if err != nil { - return nil, nil, err - } - switch { - case config.TxIndex.IndexTags != "": - txIndexer = kv.NewTxIndex(store, kv.IndexTags(splitAndTrimEmpty(config.TxIndex.IndexTags, ",", " "))) - case config.TxIndex.IndexAllTags: - txIndexer = kv.NewTxIndex(store, kv.IndexAllTags()) - default: - txIndexer = kv.NewTxIndex(store) - } - default: - txIndexer = &null.TxIndex{} +func createAndStartEventStoreService( + cfg *cfg.Config, + evsw events.EventSwitch, + logger log.Logger, +) (*eventstore.Service, eventstore.TxEventStore, error) { + var ( + err error + txEventStore eventstore.TxEventStore + ) + + // Instantiate the event store based on the configuration + switch cfg.TxEventStore.EventStoreType { + case file.EventStoreType: + // Transaction events should be logged to files + txEventStore, err = file.NewTxEventStore(cfg.TxEventStore) + if err != nil { + return nil, nil, fmt.Errorf("unable to create file tx event store, %w", err) } - */ + default: + // Transaction event storing should be omitted + txEventStore = null.NewNullEventStore() + } - indexerService := txindex.NewIndexerService(txIndexer, evsw) - indexerService.SetLogger(logger.With("module", "txindex")) + indexerService := eventstore.NewEventStoreService(txEventStore, evsw) + indexerService.SetLogger(logger.With("module", "eventstore")) if err := indexerService.Start(); err != nil { return nil, nil, err } - return indexerService, txIndexer, nil + + return indexerService, txEventStore, nil } func doHandshake(stateDB dbm.DB, state sm.State, blockStore sm.BlockStore, @@ -431,14 +432,14 @@ func NewNode(config *cfg.Config, return nil, err } - // EventSwitch and IndexerService must be started before the handshake because - // we might need to index the txs of the replayed block as this might not have happened + // EventSwitch and EventStoreService must be started before the handshake because + // we might need to store the txs of the replayed block as this might not have happened // when the node stopped last time (i.e. the node stopped after it saved the block // but before it indexed the txs, or, endblocker panicked) evsw := events.NewEventSwitch() - // Transaction indexing - indexerService, txIndexer, err := createAndStartIndexerService(config, dbProvider, evsw, logger) + // Transaction event storing + eventStoreService, txEventStore, err := createAndStartEventStoreService(config, evsw, logger) if err != nil { return nil, err } @@ -500,7 +501,7 @@ func NewNode(config *cfg.Config, privValidator, fastSync, evsw, consensusLogger, ) - nodeInfo, err := makeNodeInfo(config, nodeKey, txIndexer, genDoc, state) + nodeInfo, err := makeNodeInfo(config, nodeKey, txEventStore, genDoc, state) if err != nil { return nil, errors.Wrap(err, "error making NodeInfo") } @@ -541,17 +542,17 @@ func NewNode(config *cfg.Config, nodeInfo: nodeInfo, nodeKey: nodeKey, - evsw: evsw, - stateDB: stateDB, - blockStore: blockStore, - bcReactor: bcReactor, - mempoolReactor: mempoolReactor, - mempool: mempool, - consensusState: consensusState, - consensusReactor: consensusReactor, - proxyApp: proxyApp, - txIndexer: txIndexer, - indexerService: indexerService, + evsw: evsw, + stateDB: stateDB, + blockStore: blockStore, + bcReactor: bcReactor, + mempoolReactor: mempoolReactor, + mempool: mempool, + consensusState: consensusState, + consensusReactor: consensusReactor, + proxyApp: proxyApp, + txEventStore: txEventStore, + eventStoreService: eventStoreService, } node.BaseService = *service.NewBaseService(logger, "Node", node) @@ -626,7 +627,7 @@ func (n *Node) OnStop() { // first stop the non-reactor services n.evsw.Stop() - n.indexerService.Stop() + n.eventStoreService.Stop() // now stop the reactors n.sw.Stop() @@ -664,7 +665,6 @@ func (n *Node) ConfigureRPC() { rpccore.SetPubKey(pubKey) rpccore.SetGenesisDoc(n.genesisDoc) rpccore.SetProxyAppQuery(n.proxyApp.Query()) - rpccore.SetTxIndexer(n.txIndexer) rpccore.SetConsensusReactor(n.consensusReactor) rpccore.SetLogger(n.Logger.With("module", "rpc")) rpccore.SetEventSwitch(n.evsw) @@ -839,15 +839,13 @@ func (n *Node) NodeInfo() p2p.NodeInfo { func makeNodeInfo( config *cfg.Config, nodeKey *p2p.NodeKey, - txIndexer txindex.TxIndexer, + txEventStore eventstore.TxEventStore, genDoc *types.GenesisDoc, state sm.State, ) (p2p.NodeInfo, error) { - txIndexerStatus := "on" - if _, ok := txIndexer.(*null.TxIndex); ok { - txIndexerStatus = "off" - } else if txIndexer == nil { - txIndexerStatus = "none" + txIndexerStatus := eventstore.StatusOff + if txEventStore.GetType() != null.EventStoreType { + txIndexerStatus = eventstore.StatusOn } bcChannel := bc.BlockchainChannel diff --git a/tm2/pkg/bft/privval/file_test.go b/tm2/pkg/bft/privval/file_test.go index 306db4177e5..a798a7ddc64 100644 --- a/tm2/pkg/bft/privval/file_test.go +++ b/tm2/pkg/bft/privval/file_test.go @@ -3,7 +3,6 @@ package privval import ( "encoding/base64" "fmt" - "io/ioutil" "os" "testing" "time" @@ -18,11 +17,13 @@ import ( ) func TestGenLoadValidator(t *testing.T) { + t.Parallel() + assert := assert.New(t) - tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") + tempKeyFile, err := os.CreateTemp("", "priv_validator_key_") require.Nil(t, err) - tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + tempStateFile, err := os.CreateTemp("", "priv_validator_state_") require.Nil(t, err) privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) @@ -38,9 +39,11 @@ func TestGenLoadValidator(t *testing.T) { } func TestResetValidator(t *testing.T) { - tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") + t.Parallel() + + tempKeyFile, err := os.CreateTemp("", "priv_validator_key_") require.Nil(t, err) - tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + tempStateFile, err := os.CreateTemp("", "priv_validator_state_") require.Nil(t, err) privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) @@ -66,11 +69,13 @@ func TestResetValidator(t *testing.T) { } func TestLoadOrGenValidator(t *testing.T) { + t.Parallel() + assert := assert.New(t) - tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") + tempKeyFile, err := os.CreateTemp("", "priv_validator_key_") require.Nil(t, err) - tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + tempStateFile, err := os.CreateTemp("", "priv_validator_state_") require.Nil(t, err) tempKeyFilePath := tempKeyFile.Name() @@ -89,6 +94,8 @@ func TestLoadOrGenValidator(t *testing.T) { } func TestUnmarshalValidatorState(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) // create some fixed values @@ -114,6 +121,8 @@ func TestUnmarshalValidatorState(t *testing.T) { } func TestUnmarshalValidatorKey(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) // create some fixed values @@ -158,11 +167,13 @@ func TestUnmarshalValidatorKey(t *testing.T) { } func TestSignVote(t *testing.T) { + t.Parallel() + assert := assert.New(t) - tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") + tempKeyFile, err := os.CreateTemp("", "priv_validator_key_") require.Nil(t, err) - tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + tempStateFile, err := os.CreateTemp("", "priv_validator_state_") require.Nil(t, err) privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) @@ -204,11 +215,13 @@ func TestSignVote(t *testing.T) { } func TestSignProposal(t *testing.T) { + t.Parallel() + assert := assert.New(t) - tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") + tempKeyFile, err := os.CreateTemp("", "priv_validator_key_") require.Nil(t, err) - tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + tempStateFile, err := os.CreateTemp("", "priv_validator_state_") require.Nil(t, err) privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) @@ -248,9 +261,11 @@ func TestSignProposal(t *testing.T) { } func TestDifferByTimestamp(t *testing.T) { - tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") + t.Parallel() + + tempKeyFile, err := os.CreateTemp("", "priv_validator_key_") require.Nil(t, err) - tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + tempStateFile, err := os.CreateTemp("", "priv_validator_state_") require.Nil(t, err) privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) diff --git a/tm2/pkg/bft/privval/signer_client_test.go b/tm2/pkg/bft/privval/signer_client_test.go index 1ba6b2e08d5..a896547aec3 100644 --- a/tm2/pkg/bft/privval/signer_client_test.go +++ b/tm2/pkg/bft/privval/signer_client_test.go @@ -53,6 +53,8 @@ func getSignerTestCases(t *testing.T) []signerTestCase { } func TestSignerClose(t *testing.T) { + t.Parallel() + for _, tc := range getSignerTestCases(t) { err := tc.signerClient.Close() assert.NoError(t, err) @@ -63,6 +65,8 @@ func TestSignerClose(t *testing.T) { } func TestSignerPing(t *testing.T) { + t.Parallel() + for _, tc := range getSignerTestCases(t) { defer tc.signerServer.Stop() defer tc.signerClient.Close() @@ -73,6 +77,8 @@ func TestSignerPing(t *testing.T) { } func TestSignerGetPubKey(t *testing.T) { + t.Parallel() + for _, tc := range getSignerTestCases(t) { defer tc.signerServer.Stop() defer tc.signerClient.Close() @@ -90,6 +96,8 @@ func TestSignerGetPubKey(t *testing.T) { } func TestSignerProposal(t *testing.T) { + t.Parallel() + for _, tc := range getSignerTestCases(t) { ts := time.Now() want := &types.Proposal{Timestamp: ts} @@ -106,6 +114,8 @@ func TestSignerProposal(t *testing.T) { } func TestSignerVote(t *testing.T) { + t.Parallel() + for _, tc := range getSignerTestCases(t) { ts := time.Now() want := &types.Vote{Timestamp: ts, Type: types.PrecommitType} @@ -122,6 +132,8 @@ func TestSignerVote(t *testing.T) { } func TestSignerVoteResetDeadline(t *testing.T) { + t.Parallel() + for _, tc := range getSignerTestCases(t) { ts := time.Now() want := &types.Vote{Timestamp: ts, Type: types.PrecommitType} @@ -148,6 +160,8 @@ func TestSignerVoteResetDeadline(t *testing.T) { } func TestFlappySignerVoteKeepAlive(t *testing.T) { + t.Parallel() + testutils.FilterStability(t, testutils.Flappy) for _, tc := range getSignerTestCases(t) { @@ -175,6 +189,8 @@ func TestFlappySignerVoteKeepAlive(t *testing.T) { } func TestSignerSignProposalErrors(t *testing.T) { + t.Parallel() + for _, tc := range getSignerTestCases(t) { // Replace service with a mock that always fails tc.signerServer.privVal = types.NewErroringMockPV() @@ -197,6 +213,8 @@ func TestSignerSignProposalErrors(t *testing.T) { } func TestSignerSignVoteErrors(t *testing.T) { + t.Parallel() + for _, tc := range getSignerTestCases(t) { ts := time.Now() vote := &types.Vote{Timestamp: ts, Type: types.PrecommitType} @@ -243,6 +261,8 @@ func brokenHandler(privVal types.PrivValidator, request SignerMessage, chainID s } func TestSignerUnexpectedResponse(t *testing.T) { + t.Parallel() + for _, tc := range getSignerTestCases(t) { tc.signerServer.privVal = types.NewMockPV() tc.mockPV = types.NewMockPV() diff --git a/tm2/pkg/bft/privval/signer_listener_endpoint_test.go b/tm2/pkg/bft/privval/signer_listener_endpoint_test.go index 8e5b1613454..16640583364 100644 --- a/tm2/pkg/bft/privval/signer_listener_endpoint_test.go +++ b/tm2/pkg/bft/privval/signer_listener_endpoint_test.go @@ -32,6 +32,8 @@ type dialerTestCase struct { // SignerDialerEndpoint.dialer() call inside SignerDialerEndpoint.acceptNewConnection() to return // successfully immediately, putting an instant stop to any retry attempts. func TestSignerRemoteRetryTCPOnly(t *testing.T) { + t.Parallel() + var ( attemptCh = make(chan int) retries = 10 @@ -83,6 +85,8 @@ func TestSignerRemoteRetryTCPOnly(t *testing.T) { } func TestRetryConnToRemoteSigner(t *testing.T) { + t.Parallel() + for _, tc := range getDialerTestCases(t) { var ( logger = log.TestingLogger() @@ -130,7 +134,7 @@ func TestRetryConnToRemoteSigner(t *testing.T) { } } -// ///////////////////////////////// +// ----------- func newSignerListenerEndpoint(logger log.Logger, ln net.Listener, timeoutReadWrite time.Duration) *SignerListenerEndpoint { var listener net.Listener diff --git a/tm2/pkg/bft/privval/socket_dialers_test.go b/tm2/pkg/bft/privval/socket_dialers_test.go index 150b9218575..a2f2cf9743b 100644 --- a/tm2/pkg/bft/privval/socket_dialers_test.go +++ b/tm2/pkg/bft/privval/socket_dialers_test.go @@ -40,6 +40,8 @@ func getDialerTestCases(t *testing.T) []dialerTestCase { } func TestIsConnTimeoutForFundamentalTimeouts(t *testing.T) { + t.Parallel() + // Generate a networking timeout tcpAddr := "127.0.0.1:34985" dialer := DialTCPFn(tcpAddr, time.Millisecond, ed25519.GenPrivKey()) @@ -49,6 +51,8 @@ func TestIsConnTimeoutForFundamentalTimeouts(t *testing.T) { } func TestIsConnTimeoutForWrappedConnTimeouts(t *testing.T) { + t.Parallel() + tcpAddr := "127.0.0.1:34985" dialer := DialTCPFn(tcpAddr, time.Millisecond, ed25519.GenPrivKey()) _, err := dialer() diff --git a/tm2/pkg/bft/privval/socket_listeners_test.go b/tm2/pkg/bft/privval/socket_listeners_test.go index a3630903197..f43ec6e1636 100644 --- a/tm2/pkg/bft/privval/socket_listeners_test.go +++ b/tm2/pkg/bft/privval/socket_listeners_test.go @@ -1,7 +1,6 @@ package privval import ( - "io/ioutil" "net" "os" "testing" @@ -29,7 +28,7 @@ type listenerTestCase struct { // testUnixAddr will attempt to obtain a platform-independent temporary file // name for a Unix socket func testUnixAddr() (string, error) { - f, err := ioutil.TempFile("", "tendermint-privval-test-*") + f, err := os.CreateTemp("", "tendermint-privval-test-*") if err != nil { return "", err } @@ -89,6 +88,8 @@ func listenerTestCases(t *testing.T, timeoutAccept, timeoutReadWrite time.Durati } func TestListenerTimeoutAccept(t *testing.T) { + t.Parallel() + for _, tc := range listenerTestCases(t, time.Millisecond, time.Second) { _, err := tc.listener.Accept() opErr, ok := err.(*net.OpError) @@ -103,6 +104,8 @@ func TestListenerTimeoutAccept(t *testing.T) { } func TestListenerTimeoutReadWrite(t *testing.T) { + t.Parallel() + const ( // This needs to be long enough s.t. the Accept will definitely succeed: timeoutAccept = time.Second diff --git a/tm2/pkg/bft/privval/utils_test.go b/tm2/pkg/bft/privval/utils_test.go index 6e5562e4cbd..63ee6a6076a 100644 --- a/tm2/pkg/bft/privval/utils_test.go +++ b/tm2/pkg/bft/privval/utils_test.go @@ -9,6 +9,8 @@ import ( ) func TestIsConnTimeoutForNonTimeoutErrors(t *testing.T) { + t.Parallel() + assert.False(t, IsConnTimeout(errors.Wrap(ErrDialRetryMax, "max retries exceeded"))) assert.False(t, IsConnTimeout(errors.New("completely irrelevant error"))) } diff --git a/tm2/pkg/bft/rpc/client/helpers_test.go b/tm2/pkg/bft/rpc/client/helpers_test.go index a58ed5adfd6..4d0b54c2358 100644 --- a/tm2/pkg/bft/rpc/client/helpers_test.go +++ b/tm2/pkg/bft/rpc/client/helpers_test.go @@ -15,6 +15,8 @@ import ( ) func TestWaitForHeight(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) // test with error result - immediate failure diff --git a/tm2/pkg/bft/rpc/client/main_test.go b/tm2/pkg/bft/rpc/client/main_test.go index 58dd9538412..759104a3029 100644 --- a/tm2/pkg/bft/rpc/client/main_test.go +++ b/tm2/pkg/bft/rpc/client/main_test.go @@ -1,7 +1,6 @@ package client_test import ( - "io/ioutil" "os" "testing" @@ -14,7 +13,7 @@ var node *nm.Node func TestMain(m *testing.M) { // start a tendermint node (and kvstore) in the background to test against - dir, err := ioutil.TempDir("/tmp", "rpc-client-test") + dir, err := os.MkdirTemp("/tmp", "rpc-client-test") if err != nil { panic(err) } diff --git a/tm2/pkg/bft/rpc/client/mock/client.go b/tm2/pkg/bft/rpc/client/mock/client.go index f7a617da9fe..46db69debb3 100644 --- a/tm2/pkg/bft/rpc/client/mock/client.go +++ b/tm2/pkg/bft/rpc/client/mock/client.go @@ -50,7 +50,7 @@ type Call struct { Error error } -// GetResponse will generate the apporiate response for us, when +// GetResponse will generate the appropriate response for us, when // using the Call struct to configure a Mock handler. // // When configuring a response, if only one of Response or Error is diff --git a/tm2/pkg/bft/rpc/client/rpc_test.go b/tm2/pkg/bft/rpc/client/rpc_test.go index 7b649e1dda8..e09ae8d4466 100644 --- a/tm2/pkg/bft/rpc/client/rpc_test.go +++ b/tm2/pkg/bft/rpc/client/rpc_test.go @@ -35,6 +35,8 @@ func GetClients() []client.Client { } func TestNilCustomHTTPClient(t *testing.T) { + t.Parallel() + require.Panics(t, func() { client.NewHTTPWithClient("http://example.com", "/websocket", nil) }) @@ -44,6 +46,8 @@ func TestNilCustomHTTPClient(t *testing.T) { } func TestCustomHTTPClient(t *testing.T) { + t.Parallel() + remote := rpctest.GetConfig().RPC.ListenAddress c := client.NewHTTPWithClient(remote, "/websocket", http.DefaultClient) status, err := c.Status() @@ -52,6 +56,8 @@ func TestCustomHTTPClient(t *testing.T) { } func TestCorsEnabled(t *testing.T) { + t.Parallel() + origin := rpctest.GetConfig().RPC.CORSAllowedOrigins[0] remote := strings.Replace(rpctest.GetConfig().RPC.ListenAddress, "tcp", "http", -1) @@ -68,6 +74,8 @@ func TestCorsEnabled(t *testing.T) { // Make sure status is correct (we connect properly) func TestStatus(t *testing.T) { + t.Parallel() + for i, c := range GetClients() { moniker := rpctest.GetConfig().Moniker status, err := c.Status() @@ -78,6 +86,8 @@ func TestStatus(t *testing.T) { // Make sure info is correct (we connect properly) func TestInfo(t *testing.T) { + t.Parallel() + for i, c := range GetClients() { // status, err := c.Status() // require.Nil(t, err, "%+v", err) @@ -90,6 +100,8 @@ func TestInfo(t *testing.T) { } func TestNetInfo(t *testing.T) { + t.Parallel() + for i, c := range GetClients() { nc, ok := c.(client.NetworkClient) require.True(t, ok, "%d", i) @@ -101,6 +113,8 @@ func TestNetInfo(t *testing.T) { } func TestDumpConsensusState(t *testing.T) { + t.Parallel() + for i, c := range GetClients() { // FIXME: fix server so it doesn't panic on invalid input nc, ok := c.(client.NetworkClient) @@ -113,6 +127,8 @@ func TestDumpConsensusState(t *testing.T) { } func TestConsensusState(t *testing.T) { + t.Parallel() + for i, c := range GetClients() { // FIXME: fix server so it doesn't panic on invalid input nc, ok := c.(client.NetworkClient) @@ -124,6 +140,8 @@ func TestConsensusState(t *testing.T) { } func TestHealth(t *testing.T) { + t.Parallel() + for i, c := range GetClients() { nc, ok := c.(client.NetworkClient) require.True(t, ok, "%d", i) @@ -133,6 +151,8 @@ func TestHealth(t *testing.T) { } func TestGenesisAndValidators(t *testing.T) { + t.Parallel() + for i, c := range GetClients() { // make sure this is the right genesis file gen, err := c.Genesis() @@ -173,6 +193,8 @@ func TestABCIQuery(t *testing.T) { // Make some app checks func TestAppCalls(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) for i, c := range GetClients() { // get an offset of height to avoid racing and guessing @@ -265,6 +287,8 @@ func TestAppCalls(t *testing.T) { } func TestBroadcastTxSync(t *testing.T) { + t.Parallel() + require := require.New(t) // TODO (melekes): use mempool which is set on RPC rather than getting it from node @@ -344,6 +368,8 @@ func TestNumUnconfirmedTxs(t *testing.T) { /* func TestTx(t *testing.T) { + t.Parallel() + // first we broadcast a tx c := getHTTPClient() _, _, tx := MakeTxKV() @@ -398,6 +424,8 @@ func TestTx(t *testing.T) { } func TestTxSearch(t *testing.T) { + t.Parallel() + // first we broadcast a tx c := getHTTPClient() _, _, tx := MakeTxKV() @@ -519,6 +547,8 @@ func testBatchedJSONRPCCalls(t *testing.T, c *client.HTTP) { } func TestBatchedJSONRPCCallsCancellation(t *testing.T) { + t.Parallel() + c := getHTTPClient() _, _, tx1 := MakeTxKV() _, _, tx2 := MakeTxKV() @@ -537,6 +567,8 @@ func TestBatchedJSONRPCCallsCancellation(t *testing.T) { } func TestSendingEmptyJSONRPCRequestBatch(t *testing.T) { + t.Parallel() + c := getHTTPClient() batch := c.NewBatch() _, err := batch.Send() @@ -544,6 +576,8 @@ func TestSendingEmptyJSONRPCRequestBatch(t *testing.T) { } func TestClearingEmptyJSONRPCRequestBatch(t *testing.T) { + t.Parallel() + c := getHTTPClient() batch := c.NewBatch() require.Zero(t, batch.Clear(), "clearing an empty batch of JSON RPC requests should result in a 0 result") diff --git a/tm2/pkg/bft/rpc/core/blocks_test.go b/tm2/pkg/bft/rpc/core/blocks_test.go index a4d689ffd23..0fcd40f6d14 100644 --- a/tm2/pkg/bft/rpc/core/blocks_test.go +++ b/tm2/pkg/bft/rpc/core/blocks_test.go @@ -8,6 +8,8 @@ import ( ) func TestBlockchainInfo(t *testing.T) { + t.Parallel() + cases := []struct { min, max int64 height int64 diff --git a/tm2/pkg/bft/rpc/core/net_test.go b/tm2/pkg/bft/rpc/core/net_test.go index 42bf79fcce1..c3a8830cb1b 100644 --- a/tm2/pkg/bft/rpc/core/net_test.go +++ b/tm2/pkg/bft/rpc/core/net_test.go @@ -13,6 +13,8 @@ import ( ) func TestUnsafeDialSeeds(t *testing.T) { + t.Parallel() + sw := p2p.MakeSwitch(p2pcfg.DefaultP2PConfig(), 1, "testing", "123.123.123", func(n int, sw *p2p.Switch) *p2p.Switch { return sw }) err := sw.Start() @@ -43,6 +45,8 @@ func TestUnsafeDialSeeds(t *testing.T) { } func TestUnsafeDialPeers(t *testing.T) { + t.Parallel() + sw := p2p.MakeSwitch(p2pcfg.DefaultP2PConfig(), 1, "testing", "123.123.123", func(n int, sw *p2p.Switch) *p2p.Switch { return sw }) err := sw.Start() diff --git a/tm2/pkg/bft/rpc/core/pipe.go b/tm2/pkg/bft/rpc/core/pipe.go index fd6c8fc0692..abfdc300d31 100644 --- a/tm2/pkg/bft/rpc/core/pipe.go +++ b/tm2/pkg/bft/rpc/core/pipe.go @@ -10,7 +10,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/proxy" cfg "github.com/gnolang/gno/tm2/pkg/bft/rpc/config" sm "github.com/gnolang/gno/tm2/pkg/bft/state" - "github.com/gnolang/gno/tm2/pkg/bft/state/txindex" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" dbm "github.com/gnolang/gno/tm2/pkg/db" @@ -25,7 +24,7 @@ const ( maxPerPage = 100 ) -//---------------------------------------------- +// ---------------------------------------------- // These interfaces are used by RPC and must be thread safe type Consensus interface { @@ -50,7 +49,7 @@ type peers interface { Peers() p2p.IPeerSet } -//---------------------------------------------- +// ---------------------------------------------- // These package level globals come with setters // that are expected to be called only once, on startup @@ -68,7 +67,6 @@ var ( // objects pubKey crypto.PubKey genDoc *types.GenesisDoc // cache the genesis structure - txIndexer txindex.TxIndexer consensusReactor *consensus.ConsensusReactor evsw events.EventSwitch gTxDispatcher *txDispatcher @@ -115,10 +113,6 @@ func SetProxyAppQuery(appConn proxy.AppConnQuery) { proxyAppQuery = appConn } -func SetTxIndexer(indexer txindex.TxIndexer) { - txIndexer = indexer -} - func SetConsensusReactor(conR *consensus.ConsensusReactor) { consensusReactor = conR } @@ -169,12 +163,3 @@ func validatePerPage(perPage int) int { } return perPage } - -func validateSkipCount(page, perPage int) int { - skipCount := (page - 1) * perPage - if skipCount < 0 { - return 0 - } - - return skipCount -} diff --git a/tm2/pkg/bft/rpc/core/pipe_test.go b/tm2/pkg/bft/rpc/core/pipe_test.go index 3147f600734..6136f66c9d8 100644 --- a/tm2/pkg/bft/rpc/core/pipe_test.go +++ b/tm2/pkg/bft/rpc/core/pipe_test.go @@ -8,6 +8,8 @@ import ( ) func TestPaginationPage(t *testing.T) { + t.Parallel() + cases := []struct { totalCount int perPage int @@ -51,6 +53,8 @@ func TestPaginationPage(t *testing.T) { } func TestPaginationPerPage(t *testing.T) { + t.Parallel() + cases := []struct { totalCount int perPage int diff --git a/tm2/pkg/bft/rpc/core/types/responses_test.go b/tm2/pkg/bft/rpc/core/types/responses_test.go index d309a6b63a1..268a8d25c34 100644 --- a/tm2/pkg/bft/rpc/core/types/responses_test.go +++ b/tm2/pkg/bft/rpc/core/types/responses_test.go @@ -9,6 +9,8 @@ import ( ) func TestStatusIndexer(t *testing.T) { + t.Parallel() + var status *ResultStatus assert.False(t, status.TxIndexEnabled()) diff --git a/tm2/pkg/bft/rpc/lib/client/args_test.go b/tm2/pkg/bft/rpc/lib/client/args_test.go index 8f44939f0aa..2a7e749f094 100644 --- a/tm2/pkg/bft/rpc/lib/client/args_test.go +++ b/tm2/pkg/bft/rpc/lib/client/args_test.go @@ -15,6 +15,8 @@ type Foo struct { } func TestArgToJSON(t *testing.T) { + t.Parallel() + assert := assert.New(t) require := require.New(t) diff --git a/tm2/pkg/bft/rpc/lib/client/http_client.go b/tm2/pkg/bft/rpc/lib/client/http_client.go index 5a9da9ec052..c02d029f27a 100644 --- a/tm2/pkg/bft/rpc/lib/client/http_client.go +++ b/tm2/pkg/bft/rpc/lib/client/http_client.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" + "io" "net" "net/http" "net/url" @@ -201,7 +201,7 @@ func (c *JSONRPCClient) Call(method string, params map[string]interface{}, resul return nil, errors.New("server at '%s' returned %s", c.address, httpResponse.Status) } - responseBytes, err := ioutil.ReadAll(httpResponse.Body) + responseBytes, err := io.ReadAll(httpResponse.Body) if err != nil { return nil, err } @@ -238,7 +238,7 @@ func (c *JSONRPCClient) sendBatch(requests []*jsonRPCBufferedRequest) ([]interfa return nil, errors.New("server at '%s' returned %s", c.address, httpResponse.Status) } - responseBytes, err := ioutil.ReadAll(httpResponse.Body) + responseBytes, err := io.ReadAll(httpResponse.Body) if err != nil { return nil, err } @@ -332,7 +332,7 @@ func (c *URIClient) Call(method string, params map[string]interface{}, result in return nil, errors.New("server at '%s' returned %s", c.address, resp.Status) } - responseBytes, err := ioutil.ReadAll(resp.Body) + responseBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, err } diff --git a/tm2/pkg/bft/rpc/lib/client/http_client_test.go b/tm2/pkg/bft/rpc/lib/client/http_client_test.go index 9546a0c1d72..460f5b9947b 100644 --- a/tm2/pkg/bft/rpc/lib/client/http_client_test.go +++ b/tm2/pkg/bft/rpc/lib/client/http_client_test.go @@ -7,6 +7,8 @@ import ( ) func Test_parseRemoteAddr(t *testing.T) { + t.Parallel() + tt := []struct { remoteAddr string network, s, errContains string @@ -32,6 +34,8 @@ func Test_parseRemoteAddr(t *testing.T) { // and other protocols are left intact from parseRemoteAddr() func Test_makeHTTPDialer(t *testing.T) { + t.Parallel() + dl := makeHTTPDialer("https://.") _, err := dl("hello", "world") if assert.NotNil(t, err) { @@ -42,6 +46,8 @@ func Test_makeHTTPDialer(t *testing.T) { } func Test_makeHTTPDialer_noConvert(t *testing.T) { + t.Parallel() + dl := makeHTTPDialer("udp://.") _, err := dl("hello", "world") if assert.NotNil(t, err) { diff --git a/tm2/pkg/bft/rpc/lib/client/integration_test.go b/tm2/pkg/bft/rpc/lib/client/integration_test.go index 486540a989f..f3c705ecf98 100644 --- a/tm2/pkg/bft/rpc/lib/client/integration_test.go +++ b/tm2/pkg/bft/rpc/lib/client/integration_test.go @@ -19,6 +19,8 @@ import ( ) func TestWSClientReconnectWithJitter(t *testing.T) { + t.Parallel() + n := 8 maxReconnectAttempts := 3 // Max wait time is ceil(1+0.999) + ceil(2+0.999) + ceil(4+0.999) + ceil(...) = 2 + 3 + 5 = 10s + ... diff --git a/tm2/pkg/bft/rpc/lib/client/ws_client.go b/tm2/pkg/bft/rpc/lib/client/ws_client.go index 040437d11ff..4e159a3e3dc 100644 --- a/tm2/pkg/bft/rpc/lib/client/ws_client.go +++ b/tm2/pkg/bft/rpc/lib/client/ws_client.go @@ -226,7 +226,7 @@ func (c *WSClient) CallWithArrayParams(ctx context.Context, method string, param return c.Send(ctx, request) } -/////////////////////////////////////////////////////////////////////////////// +// ----------- // Private methods func (c *WSClient) dial() error { diff --git a/tm2/pkg/bft/rpc/lib/client/ws_client_test.go b/tm2/pkg/bft/rpc/lib/client/ws_client_test.go index b3a495f25b2..47c3a50ee63 100644 --- a/tm2/pkg/bft/rpc/lib/client/ws_client_test.go +++ b/tm2/pkg/bft/rpc/lib/client/ws_client_test.go @@ -58,6 +58,8 @@ func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func TestWSClientReconnectsAfterReadFailure(t *testing.T) { + t.Parallel() + var wg sync.WaitGroup // start server @@ -91,6 +93,8 @@ func TestWSClientReconnectsAfterReadFailure(t *testing.T) { } func TestWSClientReconnectsAfterWriteFailure(t *testing.T) { + t.Parallel() + var wg sync.WaitGroup // start server @@ -121,6 +125,8 @@ func TestWSClientReconnectsAfterWriteFailure(t *testing.T) { } func TestWSClientReconnectFailure(t *testing.T) { + t.Parallel() + // start server h := &myHandler{} s := httptest.NewServer(h) @@ -172,6 +178,8 @@ func TestWSClientReconnectFailure(t *testing.T) { } func TestNotBlockingOnStop(t *testing.T) { + t.Parallel() + timeout := 2 * time.Second s := httptest.NewServer(&myHandler{}) c := startClient(t, s.Listener.Addr()) diff --git a/tm2/pkg/bft/rpc/lib/rpc_test.go b/tm2/pkg/bft/rpc/lib/rpc_test.go index 2c1aebaae14..a299417187a 100644 --- a/tm2/pkg/bft/rpc/lib/rpc_test.go +++ b/tm2/pkg/bft/rpc/lib/rpc_test.go @@ -280,6 +280,8 @@ func testWithWSClient(t *testing.T, cl *client.WSClient) { // ------------- func TestServersAndClientsBasic(t *testing.T) { + t.Parallel() + serverAddrs := [...]string{tcpAddr, unixAddr} for _, addr := range serverAddrs { cl1 := client.NewURIClient(addr) @@ -309,6 +311,8 @@ func TestServersAndClientsBasic(t *testing.T) { } func TestHexStringArg(t *testing.T) { + t.Parallel() + cl := client.NewURIClient(tcpAddr) // should NOT be handled as hex val := "0xabc" @@ -318,6 +322,8 @@ func TestHexStringArg(t *testing.T) { } func TestQuotedStringArg(t *testing.T) { + t.Parallel() + cl := client.NewURIClient(tcpAddr) // should NOT be unquoted val := "\"abc\"" @@ -327,6 +333,8 @@ func TestQuotedStringArg(t *testing.T) { } func TestWSNewWSRPCFunc(t *testing.T) { + t.Parallel() + cl := client.NewWSClient(tcpAddr, websocketEndpoint) cl.SetLogger(log.TestingLogger()) err := cl.Start() @@ -352,6 +360,8 @@ func TestWSNewWSRPCFunc(t *testing.T) { } func TestWSHandlesArrayParams(t *testing.T) { + t.Parallel() + cl := client.NewWSClient(tcpAddr, websocketEndpoint) cl.SetLogger(log.TestingLogger()) err := cl.Start() @@ -377,6 +387,8 @@ func TestWSHandlesArrayParams(t *testing.T) { // TestWSClientPingPong checks that a client & server exchange pings // & pongs so connection stays alive. func TestWSClientPingPong(t *testing.T) { + t.Parallel() + cl := client.NewWSClient(tcpAddr, websocketEndpoint) cl.SetLogger(log.TestingLogger()) err := cl.Start() diff --git a/tm2/pkg/bft/rpc/lib/server/handlers.go b/tm2/pkg/bft/rpc/lib/server/handlers.go index aa3fadd4d18..40543e5f465 100644 --- a/tm2/pkg/bft/rpc/lib/server/handlers.go +++ b/tm2/pkg/bft/rpc/lib/server/handlers.go @@ -6,7 +6,7 @@ import ( "encoding/hex" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "reflect" "runtime/debug" @@ -101,7 +101,7 @@ func funcReturnTypes(f interface{}) []reflect.Type { // jsonrpc calls grab the given method's function info and runs reflect.Call func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - b, err := ioutil.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) if err != nil { WriteRPCResponseHTTP(w, types.RPCInvalidRequestError(types.JSONRPCStringID(""), errors.Wrap(err, "error reading request body"))) return diff --git a/tm2/pkg/bft/rpc/lib/server/handlers_test.go b/tm2/pkg/bft/rpc/lib/server/handlers_test.go index 8bf2cc0f7f1..e09a8a7cf96 100644 --- a/tm2/pkg/bft/rpc/lib/server/handlers_test.go +++ b/tm2/pkg/bft/rpc/lib/server/handlers_test.go @@ -3,7 +3,7 @@ package rpcserver_test import ( "bytes" "encoding/json" - "io/ioutil" + "io" "net/http" "net/http/httptest" "strings" @@ -18,11 +18,11 @@ import ( "github.com/gnolang/gno/tm2/pkg/log" ) -////////////////////////////////////////////////////////////////////////////// +// ----------- // HTTP REST API // TODO -////////////////////////////////////////////////////////////////////////////// +// ----------- // JSON-RPC over HTTP func testMux() *http.ServeMux { @@ -43,6 +43,8 @@ func statusOK(code int) bool { return code >= 200 && code <= 299 } // do not crash our RPC handlers. // See Issue https://github.com/gnolang/gno/tm2/pkg/bft/issues/708. func TestRPCParams(t *testing.T) { + t.Parallel() + mux := testMux() tests := []struct { payload string @@ -70,7 +72,7 @@ func TestRPCParams(t *testing.T) { res := rec.Result() // Always expecting back a JSONRPCResponse assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i) - blob, err := ioutil.ReadAll(res.Body) + blob, err := io.ReadAll(res.Body) if err != nil { t.Errorf("#%d: err reading body: %v", i, err) continue @@ -91,6 +93,8 @@ func TestRPCParams(t *testing.T) { } func TestJSONRPCID(t *testing.T) { + t.Parallel() + mux := testMux() tests := []struct { payload string @@ -118,7 +122,7 @@ func TestJSONRPCID(t *testing.T) { res := rec.Result() // Always expecting back a JSONRPCResponse assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i) - blob, err := ioutil.ReadAll(res.Body) + blob, err := io.ReadAll(res.Body) if err != nil { t.Errorf("#%d: err reading body: %v", i, err) continue @@ -138,6 +142,8 @@ func TestJSONRPCID(t *testing.T) { } func TestRPCNotification(t *testing.T) { + t.Parallel() + mux := testMux() body := strings.NewReader(`{"jsonrpc": "2.0", "id": ""}`) req, _ := http.NewRequest("POST", "http://localhost/", body) @@ -147,12 +153,14 @@ func TestRPCNotification(t *testing.T) { // Always expecting back a JSONRPCResponse require.True(t, statusOK(res.StatusCode), "should always return 2XX") - blob, err := ioutil.ReadAll(res.Body) + blob, err := io.ReadAll(res.Body) require.Nil(t, err, "reading from the body should not give back an error") require.Equal(t, len(blob), 0, "a notification SHOULD NOT be responded to by the server") } func TestRPCNotificationInBatch(t *testing.T) { + t.Parallel() + mux := testMux() tests := []struct { payload string @@ -182,7 +190,7 @@ func TestRPCNotificationInBatch(t *testing.T) { res := rec.Result() // Always expecting back a JSONRPCResponse assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i) - blob, err := ioutil.ReadAll(res.Body) + blob, err := io.ReadAll(res.Body) if err != nil { t.Errorf("#%d: err reading body: %v", i, err) continue @@ -219,6 +227,8 @@ func TestRPCNotificationInBatch(t *testing.T) { } func TestUnknownRPCPath(t *testing.T) { + t.Parallel() + mux := testMux() req, _ := http.NewRequest("GET", "http://localhost/unknownrpcpath", nil) rec := httptest.NewRecorder() @@ -229,10 +239,12 @@ func TestUnknownRPCPath(t *testing.T) { require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404") } -////////////////////////////////////////////////////////////////////////////// +// ----------- // JSON-RPC over WEBSOCKETS func TestWebsocketManagerHandler(t *testing.T) { + t.Parallel() + s := newWSServer() defer s.Close() diff --git a/tm2/pkg/bft/rpc/lib/server/http_server_test.go b/tm2/pkg/bft/rpc/lib/server/http_server_test.go index 6753a339981..5ab8d2890ae 100644 --- a/tm2/pkg/bft/rpc/lib/server/http_server_test.go +++ b/tm2/pkg/bft/rpc/lib/server/http_server_test.go @@ -5,7 +5,6 @@ import ( "crypto/tls" "fmt" "io" - "io/ioutil" "net" "net/http" "net/http/httptest" @@ -22,6 +21,8 @@ import ( ) func TestMaxOpenConnections(t *testing.T) { + t.Parallel() + const max = 5 // max simultaneous connections // Start the server. @@ -58,7 +59,7 @@ func TestMaxOpenConnections(t *testing.T) { return } defer r.Body.Close() - io.Copy(ioutil.Discard, r.Body) + io.Copy(io.Discard, r.Body) }() } wg.Wait() @@ -71,6 +72,8 @@ func TestMaxOpenConnections(t *testing.T) { } func TestStartHTTPAndTLSServer(t *testing.T) { + t.Parallel() + ln, err := net.Listen("tcp", "localhost:0") require.NoError(t, err) defer ln.Close() @@ -91,12 +94,14 @@ func TestStartHTTPAndTLSServer(t *testing.T) { defer res.Body.Close() assert.Equal(t, http.StatusOK, res.StatusCode) - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) require.NoError(t, err) assert.Equal(t, []byte("some body"), body) } func TestRecoverAndLogHandler(t *testing.T) { + t.Parallel() + tests := []struct { name string panicArg any @@ -164,6 +169,8 @@ func TestRecoverAndLogHandler(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var ( req, _ = http.NewRequest(http.MethodGet, "", nil) resp = httptest.NewRecorder() diff --git a/tm2/pkg/bft/rpc/lib/server/parse_test.go b/tm2/pkg/bft/rpc/lib/server/parse_test.go index a28dd255f23..aece3f20f3a 100644 --- a/tm2/pkg/bft/rpc/lib/server/parse_test.go +++ b/tm2/pkg/bft/rpc/lib/server/parse_test.go @@ -13,6 +13,8 @@ import ( ) func TestParseJSONMap(t *testing.T) { + t.Parallel() + input := []byte(`{"value":"1234","height":22}`) // naive is float,string @@ -101,6 +103,8 @@ func TestParseJSONMap(t *testing.T) { } func TestParseJSONArray(t *testing.T) { + t.Parallel() + input := []byte(`["1234",22]`) // naive is float,string @@ -134,6 +138,8 @@ func TestParseJSONArray(t *testing.T) { } func TestParseJSONRPC(t *testing.T) { + t.Parallel() + demo := func(ctx *types.Context, height int, name string) {} call := NewRPCFunc(demo, "height,name") @@ -170,6 +176,8 @@ func TestParseJSONRPC(t *testing.T) { } func TestParseURI(t *testing.T) { + t.Parallel() + demo := func(ctx *types.Context, height int, name string) {} call := NewRPCFunc(demo, "height,name") diff --git a/tm2/pkg/bft/rpc/lib/types/types_test.go b/tm2/pkg/bft/rpc/lib/types/types_test.go index e047c69bd77..55ee8ed3945 100644 --- a/tm2/pkg/bft/rpc/lib/types/types_test.go +++ b/tm2/pkg/bft/rpc/lib/types/types_test.go @@ -31,6 +31,8 @@ var responseTests = []responseTest{ } func TestResponses(t *testing.T) { + t.Parallel() + assert := assert.New(t) for _, tt := range responseTests { jsonid := tt.id @@ -52,6 +54,8 @@ func TestResponses(t *testing.T) { } func TestUnmarshallResponses(t *testing.T) { + t.Parallel() + assert := assert.New(t) for _, tt := range responseTests { response := &RPCResponse{} @@ -66,6 +70,8 @@ func TestUnmarshallResponses(t *testing.T) { } func TestRPCError(t *testing.T) { + t.Parallel() + assert.Equal(t, "RPC error 12 - Badness: One worse than a code 11", fmt.Sprintf("%v", &RPCError{ Code: 12, diff --git a/tm2/pkg/bft/state/eventstore/file/file.go b/tm2/pkg/bft/state/eventstore/file/file.go new file mode 100644 index 00000000000..f4cd74721f5 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/file/file.go @@ -0,0 +1,92 @@ +package file + +import ( + "fmt" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/autofile" + storetypes "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/errors" +) + +const ( + EventStoreType = "file" + Path = "path" +) + +var ( + errMissingPath = errors.New("missing path param") + errInvalidType = errors.New("invalid config for file event store specified") +) + +// TxEventStore is the implementation of a transaction event store +// that outputs to the local filesystem +type TxEventStore struct { + headPath string + group *autofile.Group +} + +// NewTxEventStore creates a new file-based tx event store +func NewTxEventStore(cfg *storetypes.Config) (*TxEventStore, error) { + // Parse config params + if EventStoreType != cfg.EventStoreType { + return nil, errInvalidType + } + + headPath, ok := cfg.GetParam(Path).(string) + if !ok { + return nil, errMissingPath + } + + return &TxEventStore{ + headPath: headPath, + }, nil +} + +// Start starts the file transaction event store, by opening the autofile group +func (t *TxEventStore) Start() error { + // Open the group + group, err := autofile.OpenGroup(t.headPath) + if err != nil { + return fmt.Errorf("unable to open file group for writing, %w", err) + } + + t.group = group + + return nil +} + +// Stop stops the file transaction event store, by closing the autofile group +func (t *TxEventStore) Stop() error { + // Close off the group + t.group.Close() + + return nil +} + +// GetType returns the file transaction event store type +func (t *TxEventStore) GetType() string { + return EventStoreType +} + +// Append marshals the transaction using amino, and writes it to the disk +func (t *TxEventStore) Append(tx types.TxResult) error { + // Serialize the transaction using amino + txRaw, err := amino.MarshalJSON(tx) + if err != nil { + return fmt.Errorf("unable to marshal transaction, %w", err) + } + + // Write the serialized transaction info to the file group + if err = t.group.WriteLine(string(txRaw)); err != nil { + return fmt.Errorf("unable to save transaction event, %w", err) + } + + // Flush output to storage + if err := t.group.FlushAndSync(); err != nil { + return fmt.Errorf("unable to flush and sync transaction event, %w", err) + } + + return nil +} diff --git a/tm2/pkg/bft/state/eventstore/file/file_test.go b/tm2/pkg/bft/state/eventstore/file/file_test.go new file mode 100644 index 00000000000..46d87582ce4 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/file/file_test.go @@ -0,0 +1,140 @@ +package file + +import ( + "bufio" + "testing" + + "github.com/gnolang/gno/tm2/pkg/amino" + storetypes "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" +) + +// generateTestTransactions generates random transaction results +func generateTestTransactions(count int) []types.TxResult { + txs := make([]types.TxResult, count) + + for i := 0; i < count; i++ { + txs[i] = types.TxResult{} + } + + return txs +} + +func TestTxEventStore_New(t *testing.T) { + t.Parallel() + + t.Run("invalid file path specified", func(t *testing.T) { + t.Parallel() + + cfg := &storetypes.Config{ + EventStoreType: "invalid", + } + + i, err := NewTxEventStore(cfg) + + assert.Nil(t, i) + assert.ErrorIs(t, err, errInvalidType) + }) + + t.Run("invalid file path specified", func(t *testing.T) { + t.Parallel() + + cfg := &storetypes.Config{ + EventStoreType: EventStoreType, + Params: nil, + } + + i, err := NewTxEventStore(cfg) + + assert.Nil(t, i) + assert.ErrorIs(t, err, errMissingPath) + }) + + t.Run("valid file path specified", func(t *testing.T) { + t.Parallel() + + headPath := "." + + cfg := &storetypes.Config{ + EventStoreType: EventStoreType, + Params: map[string]any{ + Path: headPath, + }, + } + + i, err := NewTxEventStore(cfg) + if i == nil { + t.Fatalf("unable to create event store") + } + + assert.NoError(t, err) + assert.Equal(t, headPath, i.headPath) + assert.Equal(t, EventStoreType, i.GetType()) + }) +} + +func TestTxEventStore_Append(t *testing.T) { + t.Parallel() + + headFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(func() { + cleanup() + }) + + eventStore, err := NewTxEventStore(&storetypes.Config{ + EventStoreType: EventStoreType, + Params: map[string]any{ + Path: headFile.Name(), + }, + }) + if err != nil { + t.Fatalf("unable to create tx event store, %v", err) + } + + // Start the event store + if err = eventStore.Start(); err != nil { + t.Fatalf("unable to start event store, %v", err) + } + + t.Cleanup(func() { + // Stop the event store + if err = eventStore.Stop(); err != nil { + t.Fatalf("unable to stop event store gracefully, %v", err) + } + }) + + numTxs := 10 + txs := generateTestTransactions(numTxs) + + for _, tx := range txs { + if err = eventStore.Append(tx); err != nil { + t.Fatalf("unable to store transaction, %v", err) + } + } + + // Make sure the file group's size is valid + if eventStore.group.ReadGroupInfo().TotalSize == 0 { + t.Fatalf("invalid group size") + } + + // Open file for reading + scanner := bufio.NewScanner(headFile) + + linesRead := 0 + for scanner.Scan() { + line := scanner.Bytes() + + var txRes types.TxResult + if err = amino.UnmarshalJSON(line, &txRes); err != nil { + t.Fatalf("unable to read store line") + } + + assert.Equal(t, txs[linesRead], txRes) + + linesRead++ + } + + assert.Equal(t, numTxs, linesRead) +} diff --git a/tm2/pkg/bft/state/eventstore/mock_test.go b/tm2/pkg/bft/state/eventstore/mock_test.go new file mode 100644 index 00000000000..087d8f6e3e9 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/mock_test.go @@ -0,0 +1,89 @@ +package eventstore + +import ( + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/service" +) + +// TxEventStore // + +type ( + startDelegate func() error + stopDelegate func() error + getTypeDelegate func() string + appendDelegate func(types.TxResult) error +) + +type mockEventStore struct { + startFn startDelegate + stopFn stopDelegate + getTypeFn getTypeDelegate + appendFn appendDelegate +} + +func (m mockEventStore) Start() error { + if m.startFn != nil { + return m.startFn() + } + + return nil +} + +func (m mockEventStore) Stop() error { + if m.stopFn != nil { + return m.stopFn() + } + + return nil +} + +func (m mockEventStore) GetType() string { + if m.getTypeFn != nil { + return m.getTypeFn() + } + + return "" +} + +func (m mockEventStore) Append(result types.TxResult) error { + if m.appendFn != nil { + return m.appendFn(result) + } + + return nil +} + +// EventSwitch // + +type ( + fireEventDelegate func(events.Event) + addListenerDelegate func(string, events.EventCallback) + removeListenerDelegate func(string) +) + +type mockEventSwitch struct { + service.BaseService + + fireEventFn fireEventDelegate + addListenerFn addListenerDelegate + removeListenerFn removeListenerDelegate +} + +func (m *mockEventSwitch) FireEvent(ev events.Event) { + if m.fireEventFn != nil { + m.fireEventFn(ev) + } +} + +func (m *mockEventSwitch) AddListener(listenerID string, cb events.EventCallback) { + if m.addListenerFn != nil { + m.addListenerFn(listenerID, cb) + } +} + +func (m *mockEventSwitch) RemoveListener(listenerID string) { + if m.removeListenerFn != nil { + m.removeListenerFn(listenerID) + } +} diff --git a/tm2/pkg/bft/state/eventstore/null/null.go b/tm2/pkg/bft/state/eventstore/null/null.go new file mode 100644 index 00000000000..40e3566d89e --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/null/null.go @@ -0,0 +1,35 @@ +package null + +import ( + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore" + "github.com/gnolang/gno/tm2/pkg/bft/types" +) + +var _ eventstore.TxEventStore = (*TxEventStore)(nil) + +const ( + EventStoreType = "none" +) + +// TxEventStore acts as a /dev/null +type TxEventStore struct{} + +func NewNullEventStore() *TxEventStore { + return &TxEventStore{} +} + +func (t TxEventStore) Start() error { + return nil +} + +func (t TxEventStore) Stop() error { + return nil +} + +func (t TxEventStore) Append(_ types.TxResult) error { + return nil +} + +func (t TxEventStore) GetType() string { + return EventStoreType +} diff --git a/tm2/pkg/bft/state/eventstore/store.go b/tm2/pkg/bft/state/eventstore/store.go new file mode 100644 index 00000000000..10ef9eefc9b --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/store.go @@ -0,0 +1,24 @@ +package eventstore + +import "github.com/gnolang/gno/tm2/pkg/bft/types" + +const ( + StatusOn = "on" + StatusOff = "off" +) + +// TxEventStore stores transaction events for later processing +type TxEventStore interface { + // Start starts the transaction event store + Start() error + + // Stop stops the transaction event store + Stop() error + + // GetType returns the event store type + GetType() string + + // Append analyzes and appends a single transaction + // to the event store + Append(result types.TxResult) error +} diff --git a/tm2/pkg/bft/state/eventstore/store_service.go b/tm2/pkg/bft/state/eventstore/store_service.go new file mode 100644 index 00000000000..d6ed40c4151 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/store_service.go @@ -0,0 +1,84 @@ +package eventstore + +import ( + "context" + "fmt" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/service" +) + +// Service connects the event bus and event store together in order +// to store events coming from event bus +type Service struct { + service.BaseService + + cancelFn context.CancelFunc + + txEventStore TxEventStore + evsw events.EventSwitch +} + +// NewEventStoreService returns a new service instance +func NewEventStoreService(idr TxEventStore, evsw events.EventSwitch) *Service { + is := &Service{txEventStore: idr, evsw: evsw} + is.BaseService = *service.NewBaseService(nil, "EventStoreService", is) + + return is +} + +func (is *Service) OnStart() error { + // Create a context for the intermediary monitor service + ctx, cancelFn := context.WithCancel(context.Background()) + is.cancelFn = cancelFn + + // Start the event store + if err := is.txEventStore.Start(); err != nil { + return fmt.Errorf("unable to start transaction event store, %w", err) + } + + // Start the intermediary monitor service + go is.monitorTxEvents(ctx) + + return nil +} + +func (is *Service) OnStop() { + // Close off any routines + is.cancelFn() + + // Attempt to gracefully stop the event store + if err := is.txEventStore.Stop(); err != nil { + is.Logger.Error( + fmt.Sprintf("unable to gracefully stop event store, %v", err), + ) + } +} + +// monitorTxEvents acts as an intermediary feed service for the supplied +// event store. It relays transaction events that come from the event stream +func (is *Service) monitorTxEvents(ctx context.Context) { + // Create a subscription for transaction events + subCh := events.SubscribeToEvent(is.evsw, "tx-event-store", types.EventTx{}) + + for { + select { + case <-ctx.Done(): + return + case evRaw := <-subCh: + // Cast the event + ev, ok := evRaw.(types.EventTx) + if !ok { + is.Logger.Error("invalid transaction result type cast") + + continue + } + + // Alert the actual tx event store + if err := is.txEventStore.Append(ev.Result); err != nil { + is.Logger.Error("unable to store transaction", "err", err) + } + } + } +} diff --git a/tm2/pkg/bft/state/eventstore/store_service_test.go b/tm2/pkg/bft/state/eventstore/store_service_test.go new file mode 100644 index 00000000000..3fa5e8a7941 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/store_service_test.go @@ -0,0 +1,159 @@ +package eventstore + +import ( + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/stretchr/testify/assert" +) + +// generateTxEvents generates random transaction events +func generateTxEvents(count int) []types.EventTx { + txEvents := make([]types.EventTx, count) + + for i := 0; i < count; i++ { + txEvents[i] = types.EventTx{ + Result: types.TxResult{}, + } + } + + return txEvents +} + +func TestEventStoreService_Monitor(t *testing.T) { + t.Parallel() + + const defaultTimeout = 5 * time.Second + + var ( + startCalled = false + stopCalled = false + receivedResults = make([]types.TxResult, 0) + receivedSize atomic.Int64 + + cb events.EventCallback + cbSet atomic.Bool + + mockEventStore = &mockEventStore{ + startFn: func() error { + startCalled = true + + return nil + }, + stopFn: func() error { + stopCalled = true + + return nil + }, + appendFn: func(result types.TxResult) error { + receivedResults = append(receivedResults, result) + + // Atomic because we are accessing this size from a routine + receivedSize.Store(int64(len(receivedResults))) + + return nil + }, + } + mockEventSwitch = &mockEventSwitch{ + fireEventFn: func(event events.Event) { + // Exec the callback on event fire + cb(event) + }, + addListenerFn: func(_ string, callback events.EventCallback) { + // Attach callback + cb = callback + + // Atomic because we are accessing this info from a routine + cbSet.Store(true) + }, + } + ) + + // Create a new event store instance + i := NewEventStoreService(mockEventStore, mockEventSwitch) + if i == nil { + t.Fatal("unable to create event store service") + } + + // Start the event store + if err := i.OnStart(); err != nil { + t.Fatalf("unable to start event store, %v", err) + } + + assert.True(t, startCalled) + + t.Cleanup(func() { + // Stop the event store + i.OnStop() + + assert.True(t, stopCalled) + }) + + // Fire off the events so the event store can catch them + numEvents := 1000 + txEvents := generateTxEvents(numEvents) + + var wg sync.WaitGroup + + // Start a routine that asynchronously pushes events + wg.Add(1) + go func() { + defer wg.Done() + + timeout := time.After(defaultTimeout) + + for { + select { + case <-timeout: + return + default: + // If the callback is set, fire the events + if !cbSet.Load() { + // Listener not set yet + continue + } + + for _, event := range txEvents { + mockEventSwitch.FireEvent(event) + } + + return + } + } + }() + + // Start a routine that monitors received results + wg.Add(1) + go func() { + defer wg.Done() + + timeout := time.After(defaultTimeout) + + for { + select { + case <-timeout: + return + default: + if int(receivedSize.Load()) == numEvents { + return + } + } + } + }() + + wg.Wait() + + // Make sure all results were received + if len(receivedResults) != numEvents { + t.Fatalf("invalid number of results received, %d", len(receivedResults)) + } + + // Make sure all results match + for index, event := range txEvents { + assert.Equal(t, event.Result, receivedResults[index]) + } +} diff --git a/tm2/pkg/bft/state/eventstore/types/config.go b/tm2/pkg/bft/state/eventstore/types/config.go new file mode 100644 index 00000000000..08e25870b4d --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/types/config.go @@ -0,0 +1,29 @@ +package types + +import "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/null" + +// EventStoreParams defines the arbitrary event store config params +type EventStoreParams map[string]any + +// Config defines the specific event store configuration +type Config struct { + EventStoreType string + Params EventStoreParams +} + +// GetParam fetches the specific config param, if any. +// Returns nil if the param is not present +func (c *Config) GetParam(name string) any { + if c.Params != nil { + return c.Params[name] + } + + return nil +} + +// DefaultEventStoreConfig returns the default event store config +func DefaultEventStoreConfig() *Config { + return &Config{ + EventStoreType: null.EventStoreType, + } +} diff --git a/tm2/pkg/bft/state/eventstore/types/config_test.go b/tm2/pkg/bft/state/eventstore/types/config_test.go new file mode 100644 index 00000000000..0f5683b7c61 --- /dev/null +++ b/tm2/pkg/bft/state/eventstore/types/config_test.go @@ -0,0 +1,45 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig_GetParam(t *testing.T) { + t.Parallel() + + const paramName = "param" + + testTable := []struct { + name string + cfg *Config + + expectedParam any + }{ + { + "param not set", + &Config{}, + nil, + }, + { + "valid param set", + &Config{ + Params: map[string]any{ + paramName: 10, + }, + }, + 10, + }, + } + + for _, testCase := range testTable { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, testCase.expectedParam, testCase.cfg.GetParam(paramName)) + }) + } +} diff --git a/tm2/pkg/bft/state/execution_test.go b/tm2/pkg/bft/state/execution_test.go index 849c87d3359..25217ffccbe 100644 --- a/tm2/pkg/bft/state/execution_test.go +++ b/tm2/pkg/bft/state/execution_test.go @@ -29,6 +29,8 @@ var ( ) func TestApplyBlock(t *testing.T) { + t.Parallel() + cc := proxy.NewLocalClientCreator(kvstore.NewKVStoreApplication()) proxyApp := proxy.NewAppConns(cc) err := proxyApp.Start() @@ -53,6 +55,8 @@ func TestApplyBlock(t *testing.T) { // TestBeginBlockValidators ensures we send absent validators list. func TestBeginBlockValidators(t *testing.T) { + t.Parallel() + app := &testApp{} cc := proxy.NewLocalClientCreator(app) proxyApp := proxy.NewAppConns(cc) @@ -104,6 +108,8 @@ func TestBeginBlockValidators(t *testing.T) { } func TestValidateValidatorUpdates(t *testing.T) { + t.Parallel() + pubkey1 := ed25519.GenPrivKey().PubKey() pubkey2 := ed25519.GenPrivKey().PubKey() @@ -164,6 +170,8 @@ func TestValidateValidatorUpdates(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := sm.ValidateValidatorUpdates(tc.abciUpdates, tc.validatorParams) if tc.shouldErr { assert.Error(t, err) @@ -175,6 +183,8 @@ func TestValidateValidatorUpdates(t *testing.T) { } func TestUpdateValidators(t *testing.T) { + t.Parallel() + pubkey1 := ed25519.GenPrivKey().PubKey() val1 := types.NewValidator(pubkey1, 10) pubkey2 := ed25519.GenPrivKey().PubKey() @@ -230,6 +240,8 @@ func TestUpdateValidators(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := tc.currentSet.UpdateWithABCIValidatorUpdates(tc.abciUpdates) if tc.shouldErr { assert.Error(t, err) @@ -250,6 +262,8 @@ func TestUpdateValidators(t *testing.T) { // TestEndBlockValidatorUpdates ensures we update validator set and send an event. func TestEndBlockValidatorUpdates(t *testing.T) { + t.Parallel() + app := &testApp{} cc := proxy.NewLocalClientCreator(app) proxyApp := proxy.NewAppConns(cc) @@ -316,6 +330,8 @@ LOOP: // TestEndBlockValidatorUpdatesResultingInEmptySet checks that processing validator updates that // would result in empty set causes no panic, an error is raised and NextValidators is not updated func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { + t.Parallel() + app := &testApp{} cc := proxy.NewLocalClientCreator(app) proxyApp := proxy.NewAppConns(cc) diff --git a/tm2/pkg/bft/state/helpers_test.go b/tm2/pkg/bft/state/helpers_test.go index 219baaf0f55..ca4175185a9 100644 --- a/tm2/pkg/bft/state/helpers_test.go +++ b/tm2/pkg/bft/state/helpers_test.go @@ -8,7 +8,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/proxy" sm "github.com/gnolang/gno/tm2/pkg/bft/state" "github.com/gnolang/gno/tm2/pkg/bft/types" - tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" dbm "github.com/gnolang/gno/tm2/pkg/db" @@ -190,24 +189,7 @@ func makeHeaderPartsResponsesParams(state sm.State, params abci.ConsensusParams) return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses } -func randomGenesisDoc() *types.GenesisDoc { - pubkey := ed25519.GenPrivKey().PubKey() - return &types.GenesisDoc{ - GenesisTime: tmtime.Now(), - ChainID: "abc", - Validators: []types.GenesisValidator{ - { - Address: pubkey.Address(), - PubKey: pubkey, - Power: 10, - Name: "myval", - }, - }, - ConsensusParams: types.DefaultConsensusParams(), - } -} - -//---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- type testApp struct { abci.BaseApplication diff --git a/tm2/pkg/bft/state/state_test.go b/tm2/pkg/bft/state/state_test.go index a48f4f0a3e4..d19b8526d98 100644 --- a/tm2/pkg/bft/state/state_test.go +++ b/tm2/pkg/bft/state/state_test.go @@ -42,6 +42,8 @@ func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, sm.State) { // TestStateCopy tests the correct copying behaviour of State. func TestStateCopy(t *testing.T) { + t.Parallel() + t.Helper() tearDown, _, state := setupTestCase(t) @@ -62,6 +64,8 @@ func TestStateCopy(t *testing.T) { // TestMakeGenesisStateNilValidators tests state's consistency when genesis file's validators field is nil. func TestMakeGenesisStateNilValidators(t *testing.T) { + t.Parallel() + doc := types.GenesisDoc{ ChainID: "dummy", Validators: nil, @@ -75,6 +79,8 @@ func TestMakeGenesisStateNilValidators(t *testing.T) { // TestStateSaveLoad tests saving and loading State from a db. func TestStateSaveLoad(t *testing.T) { + t.Parallel() + tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) //nolint: vetshadow @@ -91,6 +97,8 @@ func TestStateSaveLoad(t *testing.T) { // TestABCIResponsesSaveLoad tests saving and loading ABCIResponses. func TestABCIResponsesSaveLoad1(t *testing.T) { + t.Parallel() + tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) //nolint: vetshadow @@ -129,6 +137,8 @@ func TestABCIResponsesSaveLoad1(t *testing.T) { // TestResultsSaveLoad tests saving and loading ABCI results. func TestABCIResponsesSaveLoad2(t *testing.T) { + t.Parallel() + tearDown, stateDB, _ := setupTestCase(t) defer tearDown(t) //nolint: vetshadow @@ -223,6 +233,8 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { // TestValidatorSimpleSaveLoad tests saving and loading validators. func TestValidatorSimpleSaveLoad(t *testing.T) { + t.Parallel() + tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) //nolint: vetshadow @@ -256,6 +268,8 @@ func TestValidatorSimpleSaveLoad(t *testing.T) { // TestValidatorChangesSaveLoad tests saving and loading a validator set with changes. func TestOneValidatorChangesSaveLoad(t *testing.T) { + t.Parallel() + tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) @@ -309,6 +323,8 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } func TestProposerFrequency(t *testing.T) { + t.Parallel() + // some explicit test cases testCases := []struct { powers []int64 @@ -432,6 +448,8 @@ func testProposerFreq(t *testing.T, caseNum int, valSet *types.ValidatorSet) { // TestProposerPriorityDoesNotGetResetToZero assert that we preserve accum when calling updateState // see https://github.com/tendermint/classic/issues/2718 func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { + t.Parallel() + tearDown, _, state := setupTestCase(t) defer tearDown(t) val1VotingPower := int64(10) @@ -534,6 +552,8 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { } func TestProposerPriorityProposerAlternates(t *testing.T) { + t.Parallel() + // Regression test that would fail if the inner workings of // IncrementProposerPriority change. // Additionally, make sure that same power validators alternate if both @@ -670,6 +690,8 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { } func TestLargeGenesisValidator(t *testing.T) { + t.Parallel() + tearDown, _, state := setupTestCase(t) defer tearDown(t) @@ -822,6 +844,8 @@ func TestLargeGenesisValidator(t *testing.T) { } func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { + t.Parallel() + const valSetSize = 2 tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) @@ -845,6 +869,8 @@ func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { // TestValidatorChangesSaveLoad tests saving and loading a validator set with // changes. func TestManyValidatorChangesSaveLoad(t *testing.T) { + t.Parallel() + const valSetSize = 7 tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) @@ -890,6 +916,8 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { } func TestStateMakeBlock(t *testing.T) { + t.Parallel() + tearDown, _, state := setupTestCase(t) defer tearDown(t) @@ -905,6 +933,8 @@ func TestStateMakeBlock(t *testing.T) { // TestConsensusParamsChangesSaveLoad tests saving and loading consensus params // with changes. func TestConsensusParamsChangesSaveLoad(t *testing.T) { + t.Parallel() + tearDown, stateDB, state := setupTestCase(t) defer tearDown(t) @@ -964,6 +994,8 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { } func TestApplyUpdates(t *testing.T) { + t.Parallel() + initParams := makeConsensusParams(1, 2, 3, 3, 4) cases := [...]struct { diff --git a/tm2/pkg/bft/state/store_test.go b/tm2/pkg/bft/state/store_test.go index 79282e6a311..ed3b8e63311 100644 --- a/tm2/pkg/bft/state/store_test.go +++ b/tm2/pkg/bft/state/store_test.go @@ -15,6 +15,8 @@ import ( ) func TestStoreLoadValidators(t *testing.T) { + t.Parallel() + stateDB := dbm.NewMemDB() val, _ := types.RandValidator(true, 10) vals := types.NewValidatorSet([]*types.Validator{val}) diff --git a/tm2/pkg/bft/state/txindex/indexer.go b/tm2/pkg/bft/state/txindex/indexer.go deleted file mode 100644 index 2b5b4aae220..00000000000 --- a/tm2/pkg/bft/state/txindex/indexer.go +++ /dev/null @@ -1,18 +0,0 @@ -package txindex - -// TxIndexer interface defines methods to index and search transactions. -type TxIndexer interface { /* - // AddBatch analyzes, indexes and stores a batch of transactions. - AddBatch(b *Batch) error - - // Index analyzes, indexes and stores a single transaction. - Index(result *types.TxResult) error - - // Get returns the transaction specified by hash or nil if the transaction is not indexed - // or stored. - Get(hash []byte) (*types.TxResult, error) - - // Search allows you to query for transactions. - Search(q *query.Query) ([]*types.TxResult, error) - */ -} diff --git a/tm2/pkg/bft/state/txindex/indexer_service.go b/tm2/pkg/bft/state/txindex/indexer_service.go deleted file mode 100644 index fb5c3068ae4..00000000000 --- a/tm2/pkg/bft/state/txindex/indexer_service.go +++ /dev/null @@ -1,31 +0,0 @@ -package txindex - -import ( - "github.com/gnolang/gno/tm2/pkg/events" - "github.com/gnolang/gno/tm2/pkg/service" -) - -// IndexerService connects event bus and transaction indexer together in order -// to index transactions coming from event bus. -type IndexerService struct { - service.BaseService - - idr TxIndexer - evsw events.EventSwitch -} - -// NewIndexerService returns a new service instance. -func NewIndexerService(idr TxIndexer, evsw events.EventSwitch) *IndexerService { - is := &IndexerService{idr: idr, evsw: evsw} - is.BaseService = *service.NewBaseService(nil, "IndexerService", is) - return is -} - -func (is *IndexerService) OnStart() error { - // TODO - return nil -} - -func (is *IndexerService) OnStop() { - // TODO -} diff --git a/tm2/pkg/bft/state/txindex/null/null.go b/tm2/pkg/bft/state/txindex/null/null.go deleted file mode 100644 index ed90013b9a9..00000000000 --- a/tm2/pkg/bft/state/txindex/null/null.go +++ /dev/null @@ -1,31 +0,0 @@ -package null - -import ( - "github.com/gnolang/gno/tm2/pkg/bft/state/txindex" -) - -var _ txindex.TxIndexer = (*TxIndex)(nil) - -// TxIndex acts as a /dev/null. -type TxIndex struct{} - -/* -// Get on a TxIndex is disabled and panics when invoked. -func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) { - return nil, errors.New(`Indexing is disabled (set 'tx_index = "kv"' in config)`) -} - -// AddBatch is a noop and always returns nil. -func (txi *TxIndex) AddBatch(batch *txindex.Batch) error { - return nil -} - -// Index is a noop and always returns nil. -func (txi *TxIndex) Index(result *types.TxResult) error { - return nil -} - -func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) { - return []*types.TxResult{}, nil -} -*/ diff --git a/tm2/pkg/bft/state/validation_test.go b/tm2/pkg/bft/state/validation_test.go index 94aafe92694..c1941381de7 100644 --- a/tm2/pkg/bft/state/validation_test.go +++ b/tm2/pkg/bft/state/validation_test.go @@ -19,6 +19,8 @@ import ( const validationTestsStopHeight int64 = 10 func TestValidateBlockHeader(t *testing.T) { + t.Parallel() + proxyApp := newTestApp() require.NoError(t, proxyApp.Start()) defer proxyApp.Stop() @@ -80,6 +82,8 @@ func TestValidateBlockHeader(t *testing.T) { } func TestValidateBlockCommit(t *testing.T) { + t.Parallel() + proxyApp := newTestApp() require.NoError(t, proxyApp.Start()) defer proxyApp.Stop() diff --git a/tm2/pkg/bft/store/store_test.go b/tm2/pkg/bft/store/store_test.go index 1dc0bda833c..fce566903c3 100644 --- a/tm2/pkg/bft/store/store_test.go +++ b/tm2/pkg/bft/store/store_test.go @@ -58,6 +58,8 @@ func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore, cleanupFu } func TestLoadBlockStoreStateJSON(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() bsj := &BlockStoreStateJSON{Height: 1000} @@ -69,6 +71,8 @@ func TestLoadBlockStoreStateJSON(t *testing.T) { } func TestNewBlockStore(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() db.Set(blockStoreKey, []byte(`{"height": "10000"}`)) bs := NewBlockStore(db) @@ -129,6 +133,8 @@ func TestMain(m *testing.M) { // TODO: This test should be simplified ... func TestBlockStoreSaveLoadBlock(t *testing.T) { + t.Parallel() + state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) defer cleanup() require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") @@ -325,6 +331,8 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { } func TestLoadBlockPart(t *testing.T) { + t.Parallel() + bs, db := freshBlockStore() height, index := int64(10), 1 loadPart := func() (interface{}, error) { @@ -354,6 +362,8 @@ func TestLoadBlockPart(t *testing.T) { } func TestLoadBlockMeta(t *testing.T) { + t.Parallel() + bs, db := freshBlockStore() height := int64(10) loadMeta := func() (interface{}, error) { @@ -384,6 +394,8 @@ func TestLoadBlockMeta(t *testing.T) { } func TestBlockFetchAtHeight(t *testing.T) { + t.Parallel() + state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) defer cleanup() require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") diff --git a/tm2/pkg/bft/types/block_test.go b/tm2/pkg/bft/types/block_test.go index 5b49e0c392e..3963378436b 100644 --- a/tm2/pkg/bft/types/block_test.go +++ b/tm2/pkg/bft/types/block_test.go @@ -21,6 +21,8 @@ import ( ) func TestBlockValidateBasic(t *testing.T) { + t.Parallel() + require.Error(t, (*Block)(nil).ValidateBasic()) txs := []Tx{Tx("foo"), Tx("bar")} @@ -57,6 +59,8 @@ func TestBlockValidateBasic(t *testing.T) { tc := tc i := i t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + block := MakeBlock(h, txs, commit) block.ProposerAddress = valSet.GetProposer().Address tc.malleateBlock(block) @@ -67,11 +71,15 @@ func TestBlockValidateBasic(t *testing.T) { } func TestBlockHash(t *testing.T) { + t.Parallel() + assert.Nil(t, (*Block)(nil).Hash()) assert.Nil(t, MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil).Hash()) } func TestBlockMakePartSet(t *testing.T) { + t.Parallel() + assert.Nil(t, (*Block)(nil).MakePartSet(2)) partSet := MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil).MakePartSet(1024) @@ -80,6 +88,8 @@ func TestBlockMakePartSet(t *testing.T) { } func TestBlockHashesTo(t *testing.T) { + t.Parallel() + assert.False(t, (*Block)(nil).HashesTo(nil)) lastID := makeBlockIDRandom() @@ -96,6 +106,8 @@ func TestBlockHashesTo(t *testing.T) { } func TestBlockSize(t *testing.T) { + t.Parallel() + size := MakeBlock(int64(3), []Tx{Tx("Hello World")}, nil).Size() if size <= 0 { t.Fatal("Size of the block is zero or negative") @@ -103,6 +115,8 @@ func TestBlockSize(t *testing.T) { } func TestBlockString(t *testing.T) { + t.Parallel() + assert.Equal(t, "nil-Block", (*Block)(nil).String()) assert.Equal(t, "nil-Block", (*Block)(nil).StringIndented("")) assert.Equal(t, "nil-Block", (*Block)(nil).StringShort()) @@ -135,16 +149,22 @@ func makeBlockID(hash []byte, partSetSize int, partSetHash []byte) BlockID { var nilBytes []byte func TestNilHeaderHashDoesntCrash(t *testing.T) { + t.Parallel() + assert.Equal(t, (*Header)(nil).Hash(), nilBytes) assert.Equal(t, (new(Header)).Hash(), nilBytes) } func TestNilDataHashDoesntCrash(t *testing.T) { + t.Parallel() + assert.Equal(t, (*Data)(nil).Hash(), nilBytes) assert.Equal(t, new(Data).Hash(), nilBytes) } func TestCommit(t *testing.T) { + t.Parallel() + lastID := makeBlockIDRandom() h := int64(3) voteSet, _, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1) @@ -166,6 +186,8 @@ func TestCommit(t *testing.T) { } func TestCommitValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { testName string malleateCommit func(*Commit) @@ -181,6 +203,8 @@ func TestCommitValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + com := randCommit() tc.malleateCommit(com) assert.Equal(t, tc.expectErr, com.ValidateBasic() != nil, "Validate Basic had an unexpected result") @@ -189,6 +213,8 @@ func TestCommitValidateBasic(t *testing.T) { } func TestHeaderByteSize(t *testing.T) { + t.Parallel() + // Construct a UTF-8 string of MaxChainIDLen length using the supplementary // characters. // Each supplementary character takes 4 bytes. @@ -239,6 +265,8 @@ func randCommit() *Commit { } func TestCommitToVoteSet(t *testing.T) { + t.Parallel() + lastID := makeBlockIDRandom() h := int64(3) @@ -263,6 +291,8 @@ func TestCommitToVoteSet(t *testing.T) { } func TestCommitToVoteSetWithVotesForAnotherBlockOrNilBlock(t *testing.T) { + t.Parallel() + blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash")) blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) blockID3 := makeBlockID([]byte("blockhash3"), 10000, []byte("partshash")) @@ -320,6 +350,8 @@ func TestCommitToVoteSetWithVotesForAnotherBlockOrNilBlock(t *testing.T) { } func TestSignedHeaderValidateBasic(t *testing.T) { + t.Parallel() + commit := randCommit() chainID := "𠜎" timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) @@ -360,6 +392,8 @@ func TestSignedHeaderValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + sh := SignedHeader{ Header: tc.shHeader, Commit: tc.shCommit, @@ -370,6 +404,8 @@ func TestSignedHeaderValidateBasic(t *testing.T) { } func TestBlockIDValidateBasic(t *testing.T) { + t.Parallel() + validBlockID := BlockID{ Hash: []byte{}, PartsHeader: PartSetHeader{ @@ -400,6 +436,8 @@ func TestBlockIDValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + blockID := BlockID{ Hash: tc.blockIDHash, PartsHeader: tc.blockIDPartsHeader, diff --git a/tm2/pkg/bft/types/evidence.go b/tm2/pkg/bft/types/evidence.go index 26cf1aaa8ca..c11021e3976 100644 --- a/tm2/pkg/bft/types/evidence.go +++ b/tm2/pkg/bft/types/evidence.go @@ -65,7 +65,7 @@ const ( ) // MaxEvidencePerBlock returns the maximum number of evidences -// allowed in the block and their maximum total size (limitted to 1/10th +// allowed in the block and their maximum total size (limited to 1/10th // of the maximum block size). // TODO: change to a constant, or to a fraction of the validator set size. // See https://github.com/tendermint/classic/issues/2590 diff --git a/tm2/pkg/bft/types/evidence_test.go b/tm2/pkg/bft/types/evidence_test.go index 4efc61f8be7..5c43c547b74 100644 --- a/tm2/pkg/bft/types/evidence_test.go +++ b/tm2/pkg/bft/types/evidence_test.go @@ -37,6 +37,8 @@ func makeVote(val PrivValidator, chainID string, valIndex int, height int64, rou } func TestEvidence(t *testing.T) { + t.Parallel() + val := NewMockPV() val2 := NewMockPV() @@ -83,6 +85,8 @@ func TestEvidence(t *testing.T) { } func TestDuplicatedVoteEvidence(t *testing.T) { + t.Parallel() + ev := randomDuplicatedVoteEvidence() assert.True(t, ev.Equal(ev)) @@ -90,6 +94,8 @@ func TestDuplicatedVoteEvidence(t *testing.T) { } func TestEvidenceList(t *testing.T) { + t.Parallel() + ev := randomDuplicatedVoteEvidence() evl := EvidenceList([]Evidence{ev}) @@ -99,6 +105,8 @@ func TestEvidenceList(t *testing.T) { } func TestEvidenceByteSize(t *testing.T) { + t.Parallel() + val := NewMockPV() blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt64, tmhash.Sum([]byte("partshash"))) blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt64, tmhash.Sum([]byte("partshash"))) @@ -127,6 +135,8 @@ func randomDuplicatedVoteEvidence() *DuplicateVoteEvidence { } func TestDuplicateVoteEvidenceValidation(t *testing.T) { + t.Parallel() + val := NewMockPV() blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt64, tmhash.Sum([]byte("partshash"))) blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt64, tmhash.Sum([]byte("partshash"))) @@ -151,6 +161,8 @@ func TestDuplicateVoteEvidenceValidation(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + ev := &DuplicateVoteEvidence{ PubKey: secp256k1.GenPrivKey().PubKey(), VoteA: makeVote(val, chainID, math.MaxInt64, math.MaxInt64, math.MaxInt64, 0x02, blockID), @@ -163,11 +175,15 @@ func TestDuplicateVoteEvidenceValidation(t *testing.T) { } func TestMockGoodEvidenceValidateBasic(t *testing.T) { + t.Parallel() + goodEvidence := NewMockGoodEvidence(int64(1), 1, crypto.AddressFromPreimage([]byte{1})) assert.Nil(t, goodEvidence.ValidateBasic()) } func TestMockBadEvidenceValidateBasic(t *testing.T) { + t.Parallel() + badEvidence := MockBadEvidence{MockGoodEvidence: NewMockGoodEvidence(int64(1), 1, crypto.AddressFromPreimage([]byte{1}))} assert.Nil(t, badEvidence.ValidateBasic()) } diff --git a/tm2/pkg/bft/types/genesis.go b/tm2/pkg/bft/types/genesis.go index f881e068558..c03f7acc09e 100644 --- a/tm2/pkg/bft/types/genesis.go +++ b/tm2/pkg/bft/types/genesis.go @@ -18,7 +18,17 @@ const ( MaxChainIDLen = 50 ) -//------------------------------------------------------------ +var ( + ErrEmptyChainID = errors.New("chain ID is empty") + ErrLongChainID = fmt.Errorf("chain ID cannot be longer than %d chars", MaxChainIDLen) + ErrInvalidGenesisTime = errors.New("invalid genesis time") + ErrNoValidators = errors.New("no validators in set") + ErrInvalidValidatorVotingPower = errors.New("validator has no voting power") + ErrInvalidValidatorAddress = errors.New("invalid validator address") + ErrValidatorPubKeyMismatch = errors.New("validator public key and address mismatch") +) + +// ------------------------------------------------------------ // core types for a genesis definition // NOTE: any changes to the genesis definition should // be reflected in the documentation: @@ -61,6 +71,54 @@ func (genDoc *GenesisDoc) ValidatorHash() []byte { return vset.Hash() } +// Validate validates the genesis doc +func (genDoc *GenesisDoc) Validate() error { + // Make sure the chain ID is not empty + if genDoc.ChainID == "" { + return ErrEmptyChainID + } + + // Make sure the chain ID is < max chain ID length + if len(genDoc.ChainID) > MaxChainIDLen { + return ErrLongChainID + } + + // Make sure the genesis time is valid + if genDoc.GenesisTime.IsZero() { + return ErrInvalidGenesisTime + } + + // Validate the consensus params + if consensusParamsErr := ValidateConsensusParams(genDoc.ConsensusParams); consensusParamsErr != nil { + return consensusParamsErr + } + + // Make sure there are validators in the set + if len(genDoc.Validators) == 0 { + return ErrNoValidators + } + + // Make sure the validators are valid + for _, v := range genDoc.Validators { + // Check the voting power + if v.Power == 0 { + return fmt.Errorf("%w, %s", ErrInvalidValidatorVotingPower, v.Name) + } + + // Check the address + if v.Address.IsZero() { + return fmt.Errorf("%w, %s", ErrInvalidValidatorAddress, v.Name) + } + + // Check the pub key -> address matching + if v.PubKey.Address() != v.Address { + return fmt.Errorf("%w, %s", ErrValidatorPubKeyMismatch, v.Name) + } + } + + return nil +} + // ValidateAndComplete checks that all necessary fields are present // and fills in defaults for optional fields left empty func (genDoc *GenesisDoc) ValidateAndComplete() error { @@ -95,7 +153,7 @@ func (genDoc *GenesisDoc) ValidateAndComplete() error { return nil } -//------------------------------------------------------------ +// ------------------------------------------------------------ // Make genesis state from file // GenesisDocFromJSON unmarshalls JSON data into a GenesisDoc. @@ -126,7 +184,7 @@ func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) { return genDoc, nil } -//---------------------------------------- +// ---------------------------------------- // Mock AppState (for testing) type MockAppState struct { diff --git a/tm2/pkg/bft/types/genesis_test.go b/tm2/pkg/bft/types/genesis_test.go index c8886f9bf0a..24c69c6a28e 100644 --- a/tm2/pkg/bft/types/genesis_test.go +++ b/tm2/pkg/bft/types/genesis_test.go @@ -1,9 +1,9 @@ package types import ( - "io/ioutil" "os" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -14,6 +14,8 @@ import ( ) func TestGenesisBad(t *testing.T) { + t.Parallel() + // test some bad ones from raw json testCases := [][]byte{ {}, // empty @@ -37,6 +39,8 @@ func TestGenesisBad(t *testing.T) { } func TestGenesisGood(t *testing.T) { + t.Parallel() + // test a good one by raw json genDocBytes := []byte(`{"genesis_time":"0001-01-01T00:00:00Z","chain_id":"test-chain-QDKdJr","consensus_params":null,"validators":[{"pub_key":{"@type":"/tm.PubKeyEd25519","value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="},"power":"10","name":""}],"app_hash":"","app_state":{"@type":"/tm.MockAppState","account_owner":"Bob"}}`) _, err := GenesisDocFromJSON(genDocBytes) @@ -87,7 +91,9 @@ func TestGenesisGood(t *testing.T) { } func TestGenesisSaveAs(t *testing.T) { - tmpfile, err := ioutil.TempFile("", "genesis") + t.Parallel() + + tmpfile, err := os.CreateTemp("", "genesis") require.NoError(t, err) defer os.Remove(tmpfile.Name()) @@ -114,6 +120,8 @@ func TestGenesisSaveAs(t *testing.T) { } func TestGenesisValidatorHash(t *testing.T) { + t.Parallel() + genDoc := randomGenesisDoc() assert.NotEmpty(t, genDoc.ValidatorHash()) } @@ -127,3 +135,120 @@ func randomGenesisDoc() *GenesisDoc { ConsensusParams: DefaultConsensusParams(), } } + +func TestGenesis_Validate(t *testing.T) { + t.Parallel() + + getValidTestGenesis := func() *GenesisDoc { + key := randPubKey() + + return &GenesisDoc{ + GenesisTime: time.Now(), + ChainID: "valid-chain-id", + ConsensusParams: DefaultConsensusParams(), + Validators: []GenesisValidator{ + { + Address: key.Address(), + PubKey: key, + Power: 1, + Name: "valid validator", + }, + }, + } + } + + t.Run("valid genesis", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + + require.NoError(t, g.Validate()) + }) + + t.Run("invalid chain ID (zero)", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + g.ChainID = "" + + assert.ErrorIs(t, g.Validate(), ErrEmptyChainID) + }) + + t.Run("invalid chain ID (long)", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + g.ChainID = "thischainidisunusuallylongsoitwillcausethetesttofail" + + assert.ErrorIs(t, g.Validate(), ErrLongChainID) + }) + + t.Run("invalid genesis time", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + g.GenesisTime = time.Time{} + + assert.ErrorIs(t, g.Validate(), ErrInvalidGenesisTime) + }) + + t.Run("invalid consensus params", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + g.ConsensusParams.Block.MaxTxBytes = -1 // invalid value + + assert.ErrorContains(t, g.Validate(), "MaxTxBytes") + }) + + t.Run("no validators", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + g.Validators = []GenesisValidator{} + + assert.ErrorIs(t, g.Validate(), ErrNoValidators) + }) + + t.Run("invalid validator, no voting power", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + g.Validators = []GenesisValidator{ + { + Power: 0, // no voting power + }, + } + + assert.ErrorIs(t, g.Validate(), ErrInvalidValidatorVotingPower) + }) + + t.Run("invalid validator, zero address", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + g.Validators = []GenesisValidator{ + { + Power: 1, + Address: Address{}, // zero address + }, + } + + assert.ErrorIs(t, g.Validate(), ErrInvalidValidatorAddress) + }) + + t.Run("invalid validator, public key mismatch", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + g.Validators = []GenesisValidator{ + { + Power: 1, + Address: Address{1}, + PubKey: randPubKey(), + }, + } + + assert.ErrorIs(t, g.Validate(), ErrValidatorPubKeyMismatch) + }) +} diff --git a/tm2/pkg/bft/types/params.go b/tm2/pkg/bft/types/params.go index e50f5c05b88..461d62a17b3 100644 --- a/tm2/pkg/bft/types/params.go +++ b/tm2/pkg/bft/types/params.go @@ -16,6 +16,18 @@ const ( // MaxBlockPartsCount is the maximum count of block parts. MaxBlockPartsCount = (MaxBlockSizeBytes / BlockPartSizeBytes) + 1 + + // MaxBlockTxBytes is the max size of the block transaction + MaxBlockTxBytes int64 = 1000000 // 1MB + + // MaxBlockDataBytes is the max size of the block data + MaxBlockDataBytes int64 = 2000000 // 2MB + + // MaxBlockMaxGas is the max gas limit for the block + MaxBlockMaxGas int64 = 10000000 // 10M gas + + // BlockTimeIotaMS is the block time iota (in ms) + BlockTimeIotaMS int64 = 100 // ms ) var validatorPubKeyTypeURLs = map[string]struct{}{ @@ -31,10 +43,10 @@ func DefaultConsensusParams() abci.ConsensusParams { func DefaultBlockParams() *abci.BlockParams { return &abci.BlockParams{ - MaxTxBytes: 1024 * 1024, // 1MB - MaxDataBytes: 22020096, // 21MB - MaxGas: -1, - TimeIotaMS: 1000, // 1s + MaxTxBytes: MaxBlockTxBytes, + MaxDataBytes: MaxBlockDataBytes, + MaxGas: MaxBlockMaxGas, + TimeIotaMS: BlockTimeIotaMS, } } diff --git a/tm2/pkg/bft/types/params_test.go b/tm2/pkg/bft/types/params_test.go index cb5f3244e8d..141a67b903c 100644 --- a/tm2/pkg/bft/types/params_test.go +++ b/tm2/pkg/bft/types/params_test.go @@ -16,6 +16,8 @@ var ( ) func TestConsensusParamsValidation(t *testing.T) { + t.Parallel() + testCases := []struct { params abci.ConsensusParams valid bool @@ -63,6 +65,8 @@ func makeParams( } func TestConsensusParamsHash(t *testing.T) { + t.Parallel() + params := []abci.ConsensusParams{ makeParams(4, 1024, 2, 10, valEd25519), makeParams(1, 1024, 4, 10, valEd25519), @@ -90,6 +94,8 @@ func TestConsensusParamsHash(t *testing.T) { } func TestConsensusParamsUpdate(t *testing.T) { + t.Parallel() + testCases := []struct { params abci.ConsensusParams updates abci.ConsensusParams diff --git a/tm2/pkg/bft/types/part_set_test.go b/tm2/pkg/bft/types/part_set_test.go index 2e05daed849..13ebb2f0bba 100644 --- a/tm2/pkg/bft/types/part_set_test.go +++ b/tm2/pkg/bft/types/part_set_test.go @@ -1,7 +1,7 @@ package types import ( - "io/ioutil" + "io" "testing" "github.com/stretchr/testify/assert" @@ -16,6 +16,8 @@ const ( ) func TestBasicPartSet(t *testing.T) { + t.Parallel() + // Construct random data of size partSize * 100 data := random.RandBytes(testPartSize * 100) partSet := NewPartSetFromData(data, testPartSize) @@ -54,13 +56,15 @@ func TestBasicPartSet(t *testing.T) { // Reconstruct data, assert that they are equal. data2Reader := partSet2.GetReader() - data2, err := ioutil.ReadAll(data2Reader) + data2, err := io.ReadAll(data2Reader) require.NoError(t, err) assert.Equal(t, data, data2) } func TestWrongProof(t *testing.T) { + t.Parallel() + // Construct random data of size partSize * 100 data := random.RandBytes(testPartSize * 100) partSet := NewPartSetFromData(data, testPartSize) @@ -86,6 +90,8 @@ func TestWrongProof(t *testing.T) { } func TestPartSetHeaderValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { testName string malleatePartSetHeader func(*PartSetHeader) @@ -98,6 +104,8 @@ func TestPartSetHeaderValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + data := random.RandBytes(testPartSize * 100) ps := NewPartSetFromData(data, testPartSize) psHeader := ps.Header() @@ -108,6 +116,8 @@ func TestPartSetHeaderValidateBasic(t *testing.T) { } func TestPartValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { testName string malleatePart func(*Part) @@ -128,6 +138,8 @@ func TestPartValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + data := random.RandBytes(testPartSize * 100) ps := NewPartSetFromData(data, testPartSize) part := ps.GetPart(0) diff --git a/tm2/pkg/bft/types/proposal_test.go b/tm2/pkg/bft/types/proposal_test.go index ebda33298d1..0692e9bdd6f 100644 --- a/tm2/pkg/bft/types/proposal_test.go +++ b/tm2/pkg/bft/types/proposal_test.go @@ -29,6 +29,8 @@ func init() { } func TestProposalSignable(t *testing.T) { + t.Parallel() + chainID := "test_chain_id" signBytes := testProposal.SignBytes(chainID) @@ -38,6 +40,8 @@ func TestProposalSignable(t *testing.T) { } func TestProposalString(t *testing.T) { + t.Parallel() + str := testProposal.String() expected := `Proposal{12345/23456 (010203:111:626C6F636B70, -1) 000000000000 @ 2018-02-11T07:09:22.765Z}` if str != expected { @@ -46,6 +50,8 @@ func TestProposalString(t *testing.T) { } func TestProposalVerifySignature(t *testing.T) { + t.Parallel() + privVal := NewMockPV() pubKey := privVal.GetPubKey() @@ -104,6 +110,8 @@ func BenchmarkProposalVerifySignature(b *testing.B) { } func TestProposalValidateBasic(t *testing.T) { + t.Parallel() + privVal := NewMockPV() testCases := []struct { testName string @@ -130,6 +138,8 @@ func TestProposalValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + prop := NewProposal( 4, 2, 2, blockID) diff --git a/tm2/pkg/bft/types/results_test.go b/tm2/pkg/bft/types/results_test.go index 6db7044f6ad..6b375413ac6 100644 --- a/tm2/pkg/bft/types/results_test.go +++ b/tm2/pkg/bft/types/results_test.go @@ -10,6 +10,8 @@ import ( ) func TestABCIResults(t *testing.T) { + t.Parallel() + a := ABCIResult{Error: nil, Data: nil} b := ABCIResult{Error: nil, Data: []byte{}} c := ABCIResult{Error: nil, Data: []byte("one")} @@ -46,6 +48,8 @@ func TestABCIResults(t *testing.T) { } func TestABCIResultsBytes(t *testing.T) { + t.Parallel() + results := NewResults([]abci.ResponseDeliverTx{ {ResponseBase: abci.ResponseBase{Error: nil, Data: []byte{}}}, {ResponseBase: abci.ResponseBase{Error: nil, Data: []byte("one")}}, diff --git a/tm2/pkg/bft/types/time/time_test.go b/tm2/pkg/bft/types/time/time_test.go index 1b1a30e5058..bf53e313fef 100644 --- a/tm2/pkg/bft/types/time/time_test.go +++ b/tm2/pkg/bft/types/time/time_test.go @@ -8,6 +8,8 @@ import ( ) func TestWeightedMedian(t *testing.T) { + t.Parallel() + m := make([]*WeightedTime, 3) t1 := Now() diff --git a/tm2/pkg/bft/types/tx_test.go b/tm2/pkg/bft/types/tx_test.go index 1470fab024d..375783f874f 100644 --- a/tm2/pkg/bft/types/tx_test.go +++ b/tm2/pkg/bft/types/tx_test.go @@ -25,6 +25,8 @@ func randInt(low, high int) int { } func TestTxIndex(t *testing.T) { + t.Parallel() + for i := 0; i < 20; i++ { txs := makeTxs(15, 60) for j := 0; j < len(txs); j++ { @@ -38,6 +40,8 @@ func TestTxIndex(t *testing.T) { } func TestTxIndexByHash(t *testing.T) { + t.Parallel() + for i := 0; i < 20; i++ { txs := makeTxs(15, 60) for j := 0; j < len(txs); j++ { @@ -51,6 +55,8 @@ func TestTxIndexByHash(t *testing.T) { } func TestValidTxProof(t *testing.T) { + t.Parallel() + cases := []struct { txs Txs }{ @@ -90,6 +96,8 @@ func TestValidTxProof(t *testing.T) { } func TestTxProofUnchangable(t *testing.T) { + t.Parallel() + // run the other test a bunch... for i := 0; i < 40; i++ { testTxProofUnchangable(t) diff --git a/tm2/pkg/bft/types/validator_set.go b/tm2/pkg/bft/types/validator_set.go index 617ceecaae5..80ed994ca39 100644 --- a/tm2/pkg/bft/types/validator_set.go +++ b/tm2/pkg/bft/types/validator_set.go @@ -758,7 +758,7 @@ func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID strin return nil } -//----------------- +// ----------------- // ErrTooMuchChange func IsErrTooMuchChange(err error) bool { @@ -775,7 +775,7 @@ func (e tooMuchChangeError) Error() string { return fmt.Sprintf("Invalid commit -- insufficient old voting power: got %v, needed %v", e.got, e.needed) } -//---------------- +// ---------------- func (vals *ValidatorSet) String() string { return vals.StringIndented("") @@ -802,7 +802,7 @@ func (vals *ValidatorSet) StringIndented(indent string) string { indent) } -//------------------------------------- +// ------------------------------------- // Implements sort for sorting validators by address. // Sort validators by address. @@ -822,7 +822,7 @@ func (valz ValidatorsByAddress) Swap(i, j int) { valz[j] = it } -//---------------------------------------- +// ---------------------------------------- // for testing // RandValidatorSet returns a randomized validator set, useful for testing. @@ -841,7 +841,7 @@ func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []Pr return vals, privValidators } -/////////////////////////////////////////////////////////////////////////////// +// ----------- // safe addition/subtraction func safeAdd(a, b int64) (int64, bool) { diff --git a/tm2/pkg/bft/types/validator_set_test.go b/tm2/pkg/bft/types/validator_set_test.go index a2c38026ed8..0fafb6fca9e 100644 --- a/tm2/pkg/bft/types/validator_set_test.go +++ b/tm2/pkg/bft/types/validator_set_test.go @@ -20,6 +20,8 @@ import ( ) func TestValidatorSetBasic(t *testing.T) { + t.Parallel() + // empty or nil validator lists are allowed, // but attempting to IncrementProposerPriority on them will panic. vset := NewValidatorSet([]*Validator{}) @@ -76,6 +78,8 @@ func TestValidatorSetBasic(t *testing.T) { } func TestCopy(t *testing.T) { + t.Parallel() + vset := randValidatorSet(10) vsetHash := vset.Hash() if len(vsetHash) == 0 { @@ -92,6 +96,8 @@ func TestCopy(t *testing.T) { // Test that IncrementProposerPriority requires positive times. func TestIncrementProposerPriorityPositiveTimes(t *testing.T) { + t.Parallel() + vset := NewValidatorSet([]*Validator{ newValidator([]byte("foo"), 1000), newValidator([]byte("bar"), 300), @@ -122,9 +128,11 @@ func BenchmarkValidatorSetCopy(b *testing.B) { } } -//------------------------------------------------------------------- +// ------------------------------------------------------------------- func TestProposerSelection1(t *testing.T) { + t.Parallel() + vset := NewValidatorSet([]*Validator{ newValidator([]byte("foo"), 1000), newValidator([]byte("bar"), 300), @@ -143,6 +151,8 @@ func TestProposerSelection1(t *testing.T) { } func TestProposerSelection2(t *testing.T) { + t.Parallel() + addr0 := []byte{1} addr1 := []byte{2} addr2 := []byte{3} @@ -217,6 +227,8 @@ func TestProposerSelection2(t *testing.T) { } func TestProposerSelection3(t *testing.T) { + t.Parallel() + vset := NewValidatorSet([]*Validator{ newValidator([]byte("a"), 1), newValidator([]byte("b"), 1), @@ -321,9 +333,11 @@ func (vals *ValidatorSet) fromBytes(b []byte) { } } -//------------------------------------------------------------------- +// ------------------------------------------------------------------- func TestValidatorSetTotalVotingPowerPanicsOnOverflow(t *testing.T) { + t.Parallel() + // NewValidatorSet calls IncrementProposerPriority which calls TotalVotingPower() // which should panic on overflows: shouldPanic := func() { @@ -338,6 +352,8 @@ func TestValidatorSetTotalVotingPowerPanicsOnOverflow(t *testing.T) { } func TestAvgProposerPriority(t *testing.T) { + t.Parallel() + // Create Validator set without calling IncrementProposerPriority: tcs := []struct { vs ValidatorSet @@ -356,6 +372,8 @@ func TestAvgProposerPriority(t *testing.T) { } func TestAveragingInIncrementProposerPriority(t *testing.T) { + t.Parallel() + // Test that the averaging works as expected inside of IncrementProposerPriority. // Each validator comes with zero voting power which simplifies reasoning about // the expected ProposerPriority. @@ -408,6 +426,8 @@ func TestAveragingInIncrementProposerPriority(t *testing.T) { } func TestAveragingInIncrementProposerPriorityWithVotingPower(t *testing.T) { + t.Parallel() + // Other than TestAveragingInIncrementProposerPriority this is a more complete test showing // how each ProposerPriority changes in relation to the validator's voting power respectively. // average is zero in each round: @@ -558,6 +578,8 @@ func TestAveragingInIncrementProposerPriorityWithVotingPower(t *testing.T) { } func TestSafeAdd(t *testing.T) { + t.Parallel() + f := func(a, b int64) bool { c, overflow := safeAdd(a, b) return overflow || (!overflow && c == a+b) @@ -568,21 +590,27 @@ func TestSafeAdd(t *testing.T) { } func TestSafeAddClip(t *testing.T) { + t.Parallel() + assert.EqualValues(t, math.MaxInt64, safeAddClip(math.MaxInt64, 10)) assert.EqualValues(t, math.MaxInt64, safeAddClip(math.MaxInt64, math.MaxInt64)) assert.EqualValues(t, math.MinInt64, safeAddClip(math.MinInt64, -10)) } func TestSafeSubClip(t *testing.T) { + t.Parallel() + assert.EqualValues(t, math.MinInt64, safeSubClip(math.MinInt64, 10)) assert.EqualValues(t, 0, safeSubClip(math.MinInt64, math.MinInt64)) assert.EqualValues(t, math.MinInt64, safeSubClip(math.MinInt64, math.MaxInt64)) assert.EqualValues(t, math.MaxInt64, safeSubClip(math.MaxInt64, -10)) } -//------------------------------------------------------------------- +// ------------------------------------------------------------------- func TestValidatorSetVerifyCommit(t *testing.T) { + t.Parallel() + privKey := mock.GenPrivKey() pubKey := privKey.PubKey() v1 := NewValidator(pubKey, 1000) @@ -635,6 +663,8 @@ func TestValidatorSetVerifyCommit(t *testing.T) { } func TestEmptySet(t *testing.T) { + t.Parallel() + var valList []*Validator valSet := NewValidatorSet(valList) assert.Panics(t, func() { valSet.IncrementProposerPriority(1) }) @@ -661,6 +691,8 @@ func TestEmptySet(t *testing.T) { } func TestUpdatesForNewValidatorSet(t *testing.T) { + t.Parallel() + v1 := newValidator([]byte("v1"), 100) v2 := newValidator([]byte("v2"), 100) valList := []*Validator{v1, v2} @@ -793,6 +825,8 @@ func executeValSetErrTestCase(t *testing.T, idx int, tt valSetErrTestCase) { } func TestValSetUpdatesDuplicateEntries(t *testing.T) { + t.Parallel() + testCases := []valSetErrTestCase{ // Duplicate entries in changes { // first entry is duplicated change @@ -850,6 +884,8 @@ func TestValSetUpdatesDuplicateEntries(t *testing.T) { } func TestValSetUpdatesOverflows(t *testing.T) { + t.Parallel() + maxVP := MaxTotalVotingPower testCases := []valSetErrTestCase{ { // single update leading to overflow @@ -884,6 +920,8 @@ func TestValSetUpdatesOverflows(t *testing.T) { } func TestValSetUpdatesOtherErrors(t *testing.T) { + t.Parallel() + testCases := []valSetErrTestCase{ { // update with negative voting power testValSet(2, 10), @@ -909,6 +947,8 @@ func TestValSetUpdatesOtherErrors(t *testing.T) { } func TestValSetUpdatesBasicTestsExecute(t *testing.T) { + t.Parallel() + valSetUpdatesBasicTests := []struct { startVals []testVal updateVals []testVal @@ -970,6 +1010,8 @@ func TestValSetUpdatesBasicTestsExecute(t *testing.T) { // Test that different permutations of an update give the same result. func TestValSetUpdatesOrderIndependenceTestsExecute(t *testing.T) { + t.Parallel() + // startVals - initial validators to create the set with // updateVals - a sequence of updates to be applied to the set. // updateVals is shuffled a number of times during testing to check for same resulting validator set. @@ -1031,6 +1073,8 @@ func TestValSetUpdatesOrderIndependenceTestsExecute(t *testing.T) { // This tests the private function validator_set.go:applyUpdates() function, used only for additions and changes. // Should perform a proper merge of updatedVals and startVals func TestValSetApplyUpdatesTestsExecute(t *testing.T) { + t.Parallel() + valSetUpdatesBasicTests := []struct { startVals []testVal updateVals []testVal @@ -1174,6 +1218,8 @@ func applyChangesToValSet(t *testing.T, valSet *ValidatorSet, valsLists ...[]tes } func TestValSetUpdatePriorityOrderTests(t *testing.T) { + t.Parallel() + const nMaxElections = 5000 testCases := []testVSetCfg{ diff --git a/tm2/pkg/bft/types/vote_set_test.go b/tm2/pkg/bft/types/vote_set_test.go index e3199f83edc..ca5639f2406 100644 --- a/tm2/pkg/bft/types/vote_set_test.go +++ b/tm2/pkg/bft/types/vote_set_test.go @@ -61,6 +61,8 @@ func withBlockPartsHeader(vote *Vote, blockPartsHeader PartSetHeader) *Vote { } func TestAddVote(t *testing.T) { + t.Parallel() + height, round := int64(1), 0 voteSet, _, privValidators := randVoteSet(height, round, PrevoteType, 10, 1) val0 := privValidators[0] @@ -106,6 +108,8 @@ func TestAddVote(t *testing.T) { } func Test2_3Majority(t *testing.T) { + t.Parallel() + height, round := int64(1), 0 voteSet, _, privValidators := randVoteSet(height, round, PrevoteType, 10, 1) @@ -162,6 +166,8 @@ func Test2_3Majority(t *testing.T) { } func Test2_3MajorityRedux(t *testing.T) { + t.Parallel() + height, round := int64(1), 0 voteSet, _, privValidators := randVoteSet(height, round, PrevoteType, 100, 1) @@ -267,6 +273,8 @@ func Test2_3MajorityRedux(t *testing.T) { } func TestBadVotes(t *testing.T) { + t.Parallel() + height, round := int64(1), 0 voteSet, _, privValidators := randVoteSet(height, round, PrevoteType, 10, 1) @@ -332,6 +340,8 @@ func TestBadVotes(t *testing.T) { } func TestConflicts(t *testing.T) { + t.Parallel() + height, round := int64(1), 0 voteSet, _, privValidators := randVoteSet(height, round, PrevoteType, 4, 1) blockHash1 := random.RandBytes(32) @@ -465,6 +475,8 @@ func TestConflicts(t *testing.T) { } func TestMakeCommit(t *testing.T) { + t.Parallel() + height, round := int64(1), 0 voteSet, _, privValidators := randVoteSet(height, round, PrecommitType, 10, 1) blockHash, blockPartsHeader := crypto.CRandBytes(32), PartSetHeader{123, crypto.CRandBytes(32)} diff --git a/tm2/pkg/bft/types/vote_test.go b/tm2/pkg/bft/types/vote_test.go index 1028694ebc5..8b34e474fbd 100644 --- a/tm2/pkg/bft/types/vote_test.go +++ b/tm2/pkg/bft/types/vote_test.go @@ -50,6 +50,8 @@ func exampleVote(t byte) *Vote { // This test will fail and can be removed once CommitSig contains only sigs and // timestamps. func TestVoteEncoding(t *testing.T) { + t.Parallel() + vote := examplePrecommit() commitSig := vote.CommitSig() bz1 := amino.MustMarshal(vote) @@ -58,6 +60,8 @@ func TestVoteEncoding(t *testing.T) { } func TestVoteSignable(t *testing.T) { + t.Parallel() + vote := examplePrecommit() signBytes := vote.SignBytes("test_chain_id") @@ -68,6 +72,8 @@ func TestVoteSignable(t *testing.T) { } func TestVoteSignBytesTestVectors(t *testing.T) { + t.Parallel() + tests := []struct { chainID string vote *Vote @@ -147,6 +153,8 @@ func TestVoteSignBytesTestVectors(t *testing.T) { } func TestVoteProposalNotEq(t *testing.T) { + t.Parallel() + cv := CanonicalizeVote("", &Vote{Height: 1, Round: 1}) p := CanonicalizeProposal("", &Proposal{Height: 1, Round: 1}) vb, err := amino.MarshalSized(cv) @@ -157,6 +165,8 @@ func TestVoteProposalNotEq(t *testing.T) { } func TestVoteVerifySignature(t *testing.T) { + t.Parallel() + privVal := NewMockPV() pubkey := privVal.GetPubKey() @@ -186,6 +196,8 @@ func TestVoteVerifySignature(t *testing.T) { } func TestIsVoteTypeValid(t *testing.T) { + t.Parallel() + tc := []struct { name string in SignedMsgType @@ -199,6 +211,8 @@ func TestIsVoteTypeValid(t *testing.T) { for _, tt := range tc { tt := tt t.Run(tt.name, func(st *testing.T) { + st.Parallel() + if rs := IsVoteTypeValid(tt.in); rs != tt.out { t.Errorf("Got unexpected Vote type. Expected:\n%v\nGot:\n%v", rs, tt.out) } @@ -207,6 +221,8 @@ func TestIsVoteTypeValid(t *testing.T) { } func TestVoteVerify(t *testing.T) { + t.Parallel() + privVal := NewMockPV() pubkey := privVal.GetPubKey() @@ -225,6 +241,8 @@ func TestVoteVerify(t *testing.T) { } func TestMaxVoteBytes(t *testing.T) { + t.Parallel() + // time is varint encoded so need to pick the max. // year int, month Month, day, hour, min, sec, nsec int, loc *Location timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) @@ -256,6 +274,8 @@ func TestMaxVoteBytes(t *testing.T) { } func TestVoteString(t *testing.T) { + t.Parallel() + str := examplePrecommit().String() expected := `Vote{56789:6AF1F4111082 12345/02/2(Precommit) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` if str != expected { @@ -270,6 +290,8 @@ func TestVoteString(t *testing.T) { } func TestVoteValidateBasic(t *testing.T) { + t.Parallel() + privVal := NewMockPV() testCases := []struct { @@ -289,6 +311,8 @@ func TestVoteValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + vote := examplePrecommit() err := privVal.SignVote("test_chain_id", vote) require.NoError(t, err) diff --git a/tm2/pkg/bft/wal/wal.go b/tm2/pkg/bft/wal/wal.go index b66a34df222..a4a14d638dd 100644 --- a/tm2/pkg/bft/wal/wal.go +++ b/tm2/pkg/bft/wal/wal.go @@ -436,7 +436,7 @@ OUTER_LOOP: return nil, false, nil } -// ///////////////////////////////////////////////////////////////////////////// +// ----------- // A WALWriter writes custom-encoded WAL messages to an output stream. // Each binary WAL entry is length encoded, then crc encoded, @@ -512,7 +512,7 @@ func (enc *WALWriter) WriteMeta(meta MetaMessage) error { return err } -// ///////////////////////////////////////////////////////////////////////////// +// ----------- // IsDataCorruptionError returns true if data has been corrupted inside WAL. func IsDataCorruptionError(err error) bool { diff --git a/tm2/pkg/bft/wal/wal_test.go b/tm2/pkg/bft/wal/wal_test.go index 4cb94766389..322ee82bc2b 100644 --- a/tm2/pkg/bft/wal/wal_test.go +++ b/tm2/pkg/bft/wal/wal_test.go @@ -29,7 +29,7 @@ type TestMessage struct { func (TestMessage) AssertWALMessage() {} -var testPackage = amino.RegisterPackage(amino.NewPackage( +var _ = amino.RegisterPackage(amino.NewPackage( "github.com/gnolang/gno/tm2/pkg/bft/wal", "wal", amino.GetCallersDirname(), @@ -39,6 +39,8 @@ var testPackage = amino.RegisterPackage(amino.NewPackage( )) func TestWALWriterReader(t *testing.T) { + t.Parallel() + now := tmtime.Now() msgs := []TimedWALMessage{ {Time: now, Msg: TestMessage{Duration: time.Second, Height: 1, Round: 1}}, @@ -96,6 +98,8 @@ func makeTempWAL(t *testing.T, maxMsgSize int64, walChunkSize int64) (wal *baseW } func TestWALWrite(t *testing.T) { + t.Parallel() + // Create WAL const walChunkSize = 100000 wal := makeTempWAL(t, maxTestMsgSize, walChunkSize) @@ -119,6 +123,8 @@ func TestWALWrite(t *testing.T) { } func TestWALSearchForHeight(t *testing.T) { + t.Parallel() + // Create WAL const numHeight, numRounds, dataSize = 100, 10000, 10 const walChunkSize = 100000 @@ -161,6 +167,8 @@ func TestWALSearchForHeight(t *testing.T) { } func TestWALPeriodicSync(t *testing.T) { + t.Parallel() + // Create WAL const numHeight, numRounds, dataSize = 100, 10000, 10 const walChunkSize = 100000 diff --git a/tm2/pkg/bitarray/bit_array_test.go b/tm2/pkg/bitarray/bit_array_test.go index a70de110790..614d56d22cc 100644 --- a/tm2/pkg/bitarray/bit_array_test.go +++ b/tm2/pkg/bitarray/bit_array_test.go @@ -28,6 +28,8 @@ func randBitArray(bits int) (*BitArray, []byte) { } func TestAnd(t *testing.T) { + t.Parallel() + bA1, _ := randBitArray(51) bA2, _ := randBitArray(31) bA3 := bA1.And(bA2) @@ -52,6 +54,8 @@ func TestAnd(t *testing.T) { } func TestOr(t *testing.T) { + t.Parallel() + bA1, _ := randBitArray(51) bA2, _ := randBitArray(31) bA3 := bA1.Or(bA2) @@ -76,6 +80,8 @@ func TestOr(t *testing.T) { } func TestSub(t *testing.T) { + t.Parallel() + testCases := []struct { initBA string subtractingBA string @@ -107,6 +113,8 @@ func TestSub(t *testing.T) { } func TestPickRandom(t *testing.T) { + t.Parallel() + empty16Bits := "________________" empty64Bits := empty16Bits + empty16Bits + empty16Bits + empty16Bits testCases := []struct { @@ -134,6 +142,8 @@ func TestPickRandom(t *testing.T) { } func TestBytes(t *testing.T) { + t.Parallel() + bA := NewBitArray(4) bA.SetIndex(0, true) check := func(bA *BitArray, bz []byte) { @@ -163,6 +173,8 @@ func TestBytes(t *testing.T) { } func TestEmptyFull(t *testing.T) { + t.Parallel() + ns := []int{47, 123} for _, n := range ns { bA := NewBitArray(n) @@ -179,6 +191,8 @@ func TestEmptyFull(t *testing.T) { } func TestUpdateNeverPanics(t *testing.T) { + t.Parallel() + newRandBitArray := func(n int) *BitArray { ba, _ := randBitArray(n) return ba @@ -201,6 +215,8 @@ func TestUpdateNeverPanics(t *testing.T) { } func TestNewBitArrayNeverCrashesOnNegatives(t *testing.T) { + t.Parallel() + bitList := []int{-127, -128, -1 << 31} for _, bits := range bitList { _ = NewBitArray(bits) @@ -208,6 +224,8 @@ func TestNewBitArrayNeverCrashesOnNegatives(t *testing.T) { } func TestJSONMarshalUnmarshal(t *testing.T) { + t.Parallel() + bA1 := NewBitArray(0) bA2 := NewBitArray(1) @@ -233,6 +251,8 @@ func TestJSONMarshalUnmarshal(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.bA.String(), func(t *testing.T) { + t.Parallel() + bz, err := json.Marshal(tc.bA) require.NoError(t, err) diff --git a/tm2/pkg/clist/clist_test.go b/tm2/pkg/clist/clist_test.go index 496966d2d61..2cef3ef847f 100644 --- a/tm2/pkg/clist/clist_test.go +++ b/tm2/pkg/clist/clist_test.go @@ -2,8 +2,6 @@ package clist import ( "fmt" - "runtime" - "sync/atomic" "testing" "time" @@ -12,6 +10,8 @@ import ( ) func TestPanicOnMaxLength(t *testing.T) { + t.Parallel() + maxLength := 1000 l := newWithMax(maxLength) @@ -24,6 +24,8 @@ func TestPanicOnMaxLength(t *testing.T) { } func TestSmall(t *testing.T) { + t.Parallel() + l := New() el1 := l.PushBack(1) el2 := l.PushBack(2) @@ -64,110 +66,9 @@ func TestSmall(t *testing.T) { } } -// This test is quite hacky because it relies on SetFinalizer -// which isn't guaranteed to run at all. -// -//nolint:unused,deadcode -func _TestGCFifo(t *testing.T) { - t.Helper() - - if runtime.GOARCH != "amd64" { - t.Skipf("Skipping on non-amd64 machine") - } - - const numElements = 1000000 - l := New() - gcCount := new(uint64) - - // SetFinalizer doesn't work well with circular structures, - // so we construct a trivial non-circular structure to - // track. - type value struct { - Int int - } - done := make(chan struct{}) - - for i := 0; i < numElements; i++ { - v := new(value) - v.Int = i - l.PushBack(v) - runtime.SetFinalizer(v, func(v *value) { - atomic.AddUint64(gcCount, 1) - }) - } - - for el := l.Front(); el != nil; { - l.Remove(el) - // oldEl := el - el = el.Next() - // oldEl.DetachPrev() - // oldEl.DetachNext() - } - - runtime.GC() - time.Sleep(time.Second * 3) - runtime.GC() - time.Sleep(time.Second * 3) - _ = done - - if *gcCount != numElements { - t.Errorf("Expected gcCount to be %v, got %v", numElements, - *gcCount) - } -} - -// This test is quite hacky because it relies on SetFinalizer -// which isn't guaranteed to run at all. -// -//nolint:unused,deadcode -func _TestGCRandom(t *testing.T) { - t.Helper() - - if runtime.GOARCH != "amd64" { - t.Skipf("Skipping on non-amd64 machine") - } - - const numElements = 1000000 - l := New() - gcCount := 0 - - // SetFinalizer doesn't work well with circular structures, - // so we construct a trivial non-circular structure to - // track. - type value struct { - Int int - } - - for i := 0; i < numElements; i++ { - v := new(value) - v.Int = i - l.PushBack(v) - runtime.SetFinalizer(v, func(v *value) { - gcCount++ - }) - } - - els := make([]*CElement, 0, numElements) - for el := l.Front(); el != nil; el = el.Next() { - els = append(els, el) - } - - for _, i := range random.RandPerm(numElements) { - el := els[i] - l.Remove(el) - _ = el.Next() - } - - runtime.GC() - time.Sleep(time.Second * 3) - - if gcCount != numElements { - t.Errorf("Expected gcCount to be %v, got %v", numElements, - gcCount) - } -} - func TestScanRightDeleteRandom(t *testing.T) { + t.Parallel() + const numElements = 1000 const numTimes = 100 const numScanners = 10 @@ -239,6 +140,8 @@ func TestScanRightDeleteRandom(t *testing.T) { } func TestWaitChan(t *testing.T) { + t.Parallel() + l := New() ch := l.WaitChan() diff --git a/tm2/pkg/cmap/cmap_test.go b/tm2/pkg/cmap/cmap_test.go index e0fc5a5dc4e..d9051ea18d6 100644 --- a/tm2/pkg/cmap/cmap_test.go +++ b/tm2/pkg/cmap/cmap_test.go @@ -9,6 +9,8 @@ import ( ) func TestIterateKeysWithValues(t *testing.T) { + t.Parallel() + cmap := NewCMap() for i := 1; i <= 10; i++ { @@ -39,6 +41,8 @@ func TestIterateKeysWithValues(t *testing.T) { } func TestContains(t *testing.T) { + t.Parallel() + cmap := NewCMap() cmap.Set("key1", "value1") diff --git a/tm2/pkg/commands/commands_test.go b/tm2/pkg/commands/commands_test.go index 96802b9d720..4879e667cf5 100644 --- a/tm2/pkg/commands/commands_test.go +++ b/tm2/pkg/commands/commands_test.go @@ -26,6 +26,8 @@ func (c *mockConfig) RegisterFlags(fs *flag.FlagSet) { } func TestCommandParseAndRun(t *testing.T) { + t.Parallel() + type flags struct { b bool s string @@ -215,7 +217,10 @@ func TestCommandParseAndRun(t *testing.T) { }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var ( invokedCmd string args []string @@ -416,9 +421,7 @@ func TestCommand_AddSubCommands(t *testing.T) { // Forked from peterbourgon/ff/ffcli func TestHelpUsage(t *testing.T) { - fs, _ := fftest.Pair() - var buf bytes.Buffer - fs.SetOutput(&buf) + t.Parallel() tests := []struct { name string @@ -432,7 +435,6 @@ func TestHelpUsage(t *testing.T) { shortUsage: "TestHelpUsage [flags] ", shortHelp: "Some short help", longHelp: "Some long help.", - flagSet: fs, }, expectedOutput: strings.TrimSpace(` USAGE @@ -455,7 +457,6 @@ FLAGS name: "TestHelpUsage", shortUsage: "TestHelpUsage [flags] ", shortHelp: "Some short help", - flagSet: fs, }, expectedOutput: strings.TrimSpace(` USAGE @@ -477,7 +478,6 @@ FLAGS command: &Command{ name: "TestHelpUsage", shortUsage: "TestHelpUsage [flags] ", - flagSet: fs, }, expectedOutput: strings.TrimSpace(` USAGE @@ -494,8 +494,15 @@ FLAGS }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { - buf.Reset() + t.Parallel() + + fs, _ := fftest.Pair() + var buf bytes.Buffer + fs.SetOutput(&buf) + + tt.command.flagSet = fs err := tt.command.ParseAndRun(context.Background(), []string{"-h"}) @@ -507,6 +514,8 @@ FLAGS // Forked from peterbourgon/ff/ffcli func TestNestedOutput(t *testing.T) { + t.Parallel() + var ( rootHelpOutput = "USAGE\n \n\nSUBCOMMANDS\n foo\n\n" fooHelpOutput = "USAGE\n foo\n\nSUBCOMMANDS\n bar\n\n" @@ -572,6 +581,8 @@ func TestNestedOutput(t *testing.T) { }, } { t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + var ( rootfs = flag.NewFlagSet("root", flag.ContinueOnError) foofs = flag.NewFlagSet("foo", flag.ContinueOnError) diff --git a/tm2/pkg/commands/io.go b/tm2/pkg/commands/io.go index b23455e4602..fc0d3378a88 100644 --- a/tm2/pkg/commands/io.go +++ b/tm2/pkg/commands/io.go @@ -9,21 +9,42 @@ import ( // IO holds settable command // input, output and error buffers -type IO struct { - In io.Reader +type IO interface { + // getters + In() io.Reader + Out() io.WriteCloser + Err() io.WriteCloser + + // setters and helpers + SetIn(in io.Reader) + SetOut(out io.WriteCloser) + SetErr(err io.WriteCloser) + Println(args ...interface{}) + Printf(format string, args ...interface{}) + Printfln(format string, args ...interface{}) + ErrPrintln(args ...interface{}) + ErrPrintfln(format string, args ...interface{}) + GetCheckPassword(prompts [2]string, insecure bool) (string, error) + GetConfirmation(prompt string) (bool, error) + GetPassword(prompt string, insecure bool) (string, error) + GetString(prompt string) (string, error) +} + +type IOImpl struct { + in io.Reader inBuf *bufio.Reader - Out io.WriteCloser + out io.WriteCloser outBuf *bufio.Writer - Err io.WriteCloser + err io.WriteCloser errBuf *bufio.Writer } // NewDefaultIO returns a default command io // that utilizes standard input / output / error -func NewDefaultIO() *IO { - c := &IO{} +func NewDefaultIO() IO { + c := &IOImpl{} c.SetIn(os.Stdin) c.SetOut(os.Stdout) @@ -34,17 +55,21 @@ func NewDefaultIO() *IO { // NewTestIO returns a test command io // that only sets standard input (to avoid panics) -func NewTestIO() *IO { - c := &IO{} +func NewTestIO() IO { + c := &IOImpl{} c.SetIn(os.Stdin) return c } +func (io *IOImpl) In() io.Reader { return io.in } +func (io *IOImpl) Out() io.WriteCloser { return io.out } +func (io *IOImpl) Err() io.WriteCloser { return io.err } + // SetIn sets the input reader for the command io -func (io *IO) SetIn(in io.Reader) { - io.In = in - if inbuf, ok := io.In.(*bufio.Reader); ok { +func (io *IOImpl) SetIn(in io.Reader) { + io.in = in + if inbuf, ok := io.in.(*bufio.Reader); ok { io.inBuf = inbuf return @@ -54,19 +79,19 @@ func (io *IO) SetIn(in io.Reader) { } // SetOut sets the output writer for the command io -func (io *IO) SetOut(out io.WriteCloser) { - io.Out = out - io.outBuf = bufio.NewWriter(io.Out) +func (io *IOImpl) SetOut(out io.WriteCloser) { + io.out = out + io.outBuf = bufio.NewWriter(io.out) } // SetErr sets the error writer for the command io -func (io *IO) SetErr(err io.WriteCloser) { - io.Err = err - io.errBuf = bufio.NewWriter(io.Err) +func (io *IOImpl) SetErr(err io.WriteCloser) { + io.err = err + io.errBuf = bufio.NewWriter(io.err) } // Println prints a line terminated by a newline -func (io *IO) Println(args ...interface{}) { +func (io *IOImpl) Println(args ...interface{}) { if io.outBuf == nil { return } @@ -76,7 +101,7 @@ func (io *IO) Println(args ...interface{}) { } // Printf prints a formatted string without trailing newline -func (io *IO) Printf(format string, args ...interface{}) { +func (io *IOImpl) Printf(format string, args ...interface{}) { if io.outBuf == nil { return } @@ -86,7 +111,7 @@ func (io *IO) Printf(format string, args ...interface{}) { } // Printfln prints a formatted string terminated by a newline -func (io *IO) Printfln(format string, args ...interface{}) { +func (io *IOImpl) Printfln(format string, args ...interface{}) { if io.outBuf == nil { return } @@ -97,7 +122,7 @@ func (io *IO) Printfln(format string, args ...interface{}) { // ErrPrintln prints a line terminated by a newline to // cmd.Err(Buf) -func (io *IO) ErrPrintln(args ...interface{}) { +func (io *IOImpl) ErrPrintln(args ...interface{}) { if io.errBuf == nil { return } @@ -107,7 +132,7 @@ func (io *IO) ErrPrintln(args ...interface{}) { } // ErrPrintfln prints a formatted string terminated by a newline to cmd.Err(Buf) -func (io *IO) ErrPrintfln(format string, args ...interface{}) { +func (io *IOImpl) ErrPrintfln(format string, args ...interface{}) { if io.errBuf == nil { return } diff --git a/tm2/pkg/commands/utils.go b/tm2/pkg/commands/utils.go index c7ce19e8138..90b6fa12b64 100644 --- a/tm2/pkg/commands/utils.go +++ b/tm2/pkg/commands/utils.go @@ -9,7 +9,7 @@ import ( ) // GetPassword fetches the password using the provided prompt, if any -func (io *IO) GetPassword( +func (io *IOImpl) GetPassword( prompt string, insecure bool, ) (string, error) { @@ -27,7 +27,7 @@ func (io *IO) GetPassword( } // readLine reads a new line from standard input -func (io *IO) readLine() (string, error) { +func (io *IOImpl) readLine() (string, error) { input, err := io.inBuf.ReadString('\n') if err != nil { return "", err @@ -52,7 +52,7 @@ func readPassword() (string, error) { // GetConfirmation will request user give the confirmation from stdin. // "y", "Y", "yes", "YES", and "Yes" all count as confirmations. // If the input is not recognized, it returns false and a nil error. -func (io *IO) GetConfirmation(prompt string) (bool, error) { +func (io *IOImpl) GetConfirmation(prompt string) (bool, error) { // On stderr so it isn't part of bash output. io.ErrPrintfln("%s [y/n]:", prompt) @@ -78,7 +78,7 @@ func (io *IO) GetConfirmation(prompt string) (bool, error) { // match (for creating a new password). // It enforces the password length. Only parses password once if // input is piped in. -func (io *IO) GetCheckPassword( +func (io *IOImpl) GetCheckPassword( prompts [2]string, insecure bool, ) (string, error) { @@ -100,7 +100,7 @@ func (io *IO) GetCheckPassword( } // GetString simply returns the trimmed string output of a given reader. -func (io *IO) GetString(prompt string) (string, error) { +func (io *IOImpl) GetString(prompt string) (string, error) { if prompt != "" { // On stderr so it isn't part of bash output. io.ErrPrintln(prompt) diff --git a/tm2/pkg/crypto/bcrypt/bcrypt_test.go b/tm2/pkg/crypto/bcrypt/bcrypt_test.go index 96634260d65..92b296bfe52 100644 --- a/tm2/pkg/crypto/bcrypt/bcrypt_test.go +++ b/tm2/pkg/crypto/bcrypt/bcrypt_test.go @@ -12,6 +12,8 @@ import ( ) func TestBcryptingIsEasy(t *testing.T) { + t.Parallel() + pass := []byte("mypassword") salt := []byte("1234567890123456") hp, err := GenerateFromPassword(salt, pass, 0) @@ -31,6 +33,8 @@ func TestBcryptingIsEasy(t *testing.T) { } func TestBcryptingIsCorrect(t *testing.T) { + t.Parallel() + pass := []byte("allmine") salt := []byte("XajjQvNhvvRt5GSeFk1xFe") expectedHash := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga") @@ -56,6 +60,8 @@ func TestBcryptingIsCorrect(t *testing.T) { } func TestVeryShortPasswords(t *testing.T) { + t.Parallel() + key := []byte("k") salt := []byte("XajjQvNhvvRt5GSeFk1xFe") _, err := bcrypt(key, 10, salt) @@ -65,6 +71,8 @@ func TestVeryShortPasswords(t *testing.T) { } func TestTooLongPasswordsWork(t *testing.T) { + t.Parallel() + salt := []byte("XajjQvNhvvRt5GSeFk1xFe") // One byte over the usual 56 byte limit that blowfish has tooLongPass := []byte("012345678901234567890123456789012345678901234567890123456") @@ -92,6 +100,8 @@ var invalidTests = []InvalidHashTest{ } func TestInvalidHashErrors(t *testing.T) { + t.Parallel() + check := func(name string, expected, err error) { if err == nil { t.Errorf("%s: Should have returned an error", name) @@ -109,6 +119,8 @@ func TestInvalidHashErrors(t *testing.T) { } func TestUnpaddedBase64Encoding(t *testing.T) { + t.Parallel() + original := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30} encodedOriginal := []byte("XajjQvNhvvRt5GSeFk1xFe") @@ -129,6 +141,8 @@ func TestUnpaddedBase64Encoding(t *testing.T) { } func TestCost(t *testing.T) { + t.Parallel() + suffix := "XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C" for _, vers := range []string{"2a", "2"} { for _, cost := range []int{4, 10} { @@ -151,6 +165,8 @@ func TestCost(t *testing.T) { } func TestCostValidationInHash(t *testing.T) { + t.Parallel() + if testing.Short() { return } @@ -185,6 +201,8 @@ func TestCostValidationInHash(t *testing.T) { } func TestCostReturnsWithLeadingZeroes(t *testing.T) { + t.Parallel() + salt := []byte("1234567890123456") hp, _ := newFromPassword(salt, []byte("abcdefgh"), 7) cost := hp.Hash()[4:7] @@ -196,6 +214,8 @@ func TestCostReturnsWithLeadingZeroes(t *testing.T) { } func TestMinorNotRequired(t *testing.T) { + t.Parallel() + noMinorHash := []byte("$2$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga") h, err := newFromHash(noMinorHash) if err != nil { @@ -233,6 +253,8 @@ func BenchmarkDefaultCost(b *testing.B) { // See Issue https://github.com/golang/go/issues/20425. func TestNoSideEffectsFromCompare(t *testing.T) { + t.Parallel() + source := []byte("passw0rd123456") password := source[:len(source)-6] token := source[len(source)-6:] diff --git a/tm2/pkg/crypto/bech32_test.go b/tm2/pkg/crypto/bech32_test.go index 13bdaa8e816..f5bc3e9ed7c 100644 --- a/tm2/pkg/crypto/bech32_test.go +++ b/tm2/pkg/crypto/bech32_test.go @@ -21,6 +21,8 @@ var invalidStrs = []string{ } func TestEmptyAddresses(t *testing.T) { + t.Parallel() + require.Equal(t, (crypto.Address{}).String(), "g1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqluuxe") addr := crypto.AddressFromBytes(make([]byte, 20)) @@ -47,6 +49,8 @@ func testMarshal(t *testing.T, addr crypto.Address, marshal func(orig interface{ } func TestRandBech32AddrConsistency(t *testing.T) { + t.Parallel() + var pub ed25519.PubKeyEd25519 for i := 0; i < 1000; i++ { diff --git a/tm2/pkg/crypto/bip39/bip39_test.go b/tm2/pkg/crypto/bip39/bip39_test.go index cbe15be6c3f..7854db76f53 100644 --- a/tm2/pkg/crypto/bip39/bip39_test.go +++ b/tm2/pkg/crypto/bip39/bip39_test.go @@ -14,6 +14,8 @@ type Vector struct { } func TestBip39(t *testing.T) { + t.Parallel() + for _, vector := range testVectors() { entropy, err := hex.DecodeString(vector.entropy) assert.NoError(t, err) @@ -31,6 +33,8 @@ func TestBip39(t *testing.T) { } func TestIsMnemonicValid(t *testing.T) { + t.Parallel() + for _, vector := range badMnemonicSentences() { assert.Equal(t, IsMnemonicValid(vector.mnemonic), false) } @@ -41,6 +45,8 @@ func TestIsMnemonicValid(t *testing.T) { } func TestInvalidMnemonicFails(t *testing.T) { + t.Parallel() + for _, vector := range badMnemonicSentences() { _, err := MnemonicToByteArray(vector.mnemonic) assert.NotNil(t, err) @@ -48,6 +54,8 @@ func TestInvalidMnemonicFails(t *testing.T) { } func TestValidateEntropyWithChecksumBitSize(t *testing.T) { + t.Parallel() + // Good tests. for i := 1; i <= (12*32 + 12); i++ { err := validateEntropyWithChecksumBitSize(i) @@ -74,6 +82,8 @@ func TestValidateEntropyWithChecksumBitSize(t *testing.T) { } func TestNewEntropy(t *testing.T) { + t.Parallel() + // Good tests. for i := 128; i <= 256; i += 32 { _, err := NewEntropy(i) diff --git a/tm2/pkg/crypto/ed25519/ed25519_test.go b/tm2/pkg/crypto/ed25519/ed25519_test.go index c07b7ae2298..fdf269018c0 100644 --- a/tm2/pkg/crypto/ed25519/ed25519_test.go +++ b/tm2/pkg/crypto/ed25519/ed25519_test.go @@ -10,6 +10,8 @@ import ( ) func TestSignAndValidateEd25519(t *testing.T) { + t.Parallel() + privKey := ed25519.GenPrivKey() pubKey := privKey.PubKey() diff --git a/tm2/pkg/crypto/hd/fundraiser_test.go b/tm2/pkg/crypto/hd/fundraiser_test.go index dabfd670c37..884425c6c39 100644 --- a/tm2/pkg/crypto/hd/fundraiser_test.go +++ b/tm2/pkg/crypto/hd/fundraiser_test.go @@ -43,6 +43,8 @@ func initFundraiserTestVectors(t *testing.T) []addrData { } func TestFundraiserCompatibility(t *testing.T) { + t.Parallel() + hdToAddrTable := initFundraiserTestVectors(t) for i, d := range hdToAddrTable { diff --git a/tm2/pkg/crypto/hd/hdpath_test.go b/tm2/pkg/crypto/hd/hdpath_test.go index 1e66a9fba2c..31e806b2b1a 100644 --- a/tm2/pkg/crypto/hd/hdpath_test.go +++ b/tm2/pkg/crypto/hd/hdpath_test.go @@ -34,6 +34,8 @@ const ( ) func TestStringifyFundraiserPathParams(t *testing.T) { + t.Parallel() + path := NewFundraiserParams(4, testCoinType, 22) require.Equal(t, "44'/118'/4'/0/22", path.String()) @@ -45,6 +47,8 @@ func TestStringifyFundraiserPathParams(t *testing.T) { } func TestPathToArray(t *testing.T) { + t.Parallel() + path := NewParams(44, 118, 1, false, 4) require.Equal(t, "[44 118 1 0 4]", fmt.Sprintf("%v", path.DerivationPath())) @@ -53,6 +57,8 @@ func TestPathToArray(t *testing.T) { } func TestParamsFromPath(t *testing.T) { + t.Parallel() + goodCases := []struct { params *BIP44Params path string diff --git a/tm2/pkg/crypto/keys/armor/armor.go b/tm2/pkg/crypto/keys/armor/armor.go index 7233b27123a..22315f8c521 100644 --- a/tm2/pkg/crypto/keys/armor/armor.go +++ b/tm2/pkg/crypto/keys/armor/armor.go @@ -128,7 +128,7 @@ func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privK } key = crypto.Sha256(key) // Get 32 bytes privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key) - if err != nil && err.Error() == "Ciphertext decryption failed" { + if err != nil && err.Error() == "ciphertext decryption failed" { return privKey, keyerror.NewErrWrongPassword() } else if err != nil { return privKey, err diff --git a/tm2/pkg/crypto/keys/client/add.go b/tm2/pkg/crypto/keys/client/add.go index 30b612a9de2..71dc6f03090 100644 --- a/tm2/pkg/crypto/keys/client/add.go +++ b/tm2/pkg/crypto/keys/client/add.go @@ -29,7 +29,7 @@ type addCfg struct { index uint64 } -func newAddCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newAddCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { cfg := &addCfg{ rootCfg: rootCfg, } @@ -131,7 +131,7 @@ input output - armor encrypted private key (saved to file) */ -func execAdd(cfg *addCfg, args []string, io *commands.IO) error { +func execAdd(cfg *addCfg, args []string, io commands.IO) error { var ( kb keys.Keybase err error @@ -156,8 +156,7 @@ func execAdd(cfg *addCfg, args []string, io *commands.IO) error { return err } - _, err = kb.GetByName(name) - if err == nil { + if has, err := kb.HasByName(name); err == nil && has { // account exists, ask for user confirmation response, err2 := io.GetConfirmation(fmt.Sprintf("Override the existing name %s", name)) if err2 != nil { @@ -259,7 +258,7 @@ func execAdd(cfg *addCfg, args []string, io *commands.IO) error { } if len(mnemonic) == 0 { - mnemonic, err = generateMnemonic(mnemonicEntropySize) + mnemonic, err = GenerateMnemonic(mnemonicEntropySize) if err != nil { return err } @@ -280,7 +279,7 @@ func execAdd(cfg *addCfg, args []string, io *commands.IO) error { return printCreate(info, showMnemonic, mnemonic, io) } -func printCreate(info keys.Info, showMnemonic bool, mnemonic string, io *commands.IO) error { +func printCreate(info keys.Info, showMnemonic bool, mnemonic string, io commands.IO) error { io.Println("") printNewInfo(info, io) @@ -296,7 +295,7 @@ It is the only way to recover your account if you ever forget your password. return nil } -func printNewInfo(info keys.Info, io *commands.IO) { +func printNewInfo(info keys.Info, io commands.IO) { keyname := info.GetName() keytype := info.GetType() keypub := info.GetPubKey() diff --git a/tm2/pkg/crypto/keys/client/addpkg.go b/tm2/pkg/crypto/keys/client/addpkg.go index 3de9a6de546..5bbd3f08ad0 100644 --- a/tm2/pkg/crypto/keys/client/addpkg.go +++ b/tm2/pkg/crypto/keys/client/addpkg.go @@ -24,7 +24,7 @@ type addPkgCfg struct { deposit string } -func newAddPkgCmd(rootCfg *makeTxCfg, io *commands.IO) *commands.Command { +func newAddPkgCmd(rootCfg *makeTxCfg, io commands.IO) *commands.Command { cfg := &addPkgCfg{ rootCfg: rootCfg, } @@ -65,7 +65,7 @@ func (c *addPkgCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execAddPkg(cfg *addPkgCfg, args []string, io *commands.IO) error { +func execAddPkg(cfg *addPkgCfg, args []string, io commands.IO) error { if cfg.pkgPath == "" { return errors.New("pkgpath not specified") } @@ -142,7 +142,7 @@ func signAndBroadcast( cfg *makeTxCfg, args []string, tx std.Tx, - io *commands.IO, + io commands.IO, ) error { baseopts := cfg.rootCfg txopts := cfg diff --git a/tm2/pkg/crypto/keys/client/broadcast.go b/tm2/pkg/crypto/keys/client/broadcast.go index f1d448495a6..9c05f2c43b3 100644 --- a/tm2/pkg/crypto/keys/client/broadcast.go +++ b/tm2/pkg/crypto/keys/client/broadcast.go @@ -23,7 +23,7 @@ type broadcastCfg struct { tx *std.Tx } -func newBroadcastCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newBroadcastCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { cfg := &broadcastCfg{ rootCfg: rootCfg, } @@ -50,7 +50,7 @@ func (c *broadcastCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execBroadcast(cfg *broadcastCfg, args []string, io *commands.IO) error { +func execBroadcast(cfg *broadcastCfg, args []string, io commands.IO) error { if len(args) != 1 { return flag.ErrHelp } diff --git a/tm2/pkg/crypto/keys/client/call.go b/tm2/pkg/crypto/keys/client/call.go index 29fe9739a36..6f9c9d52f5f 100644 --- a/tm2/pkg/crypto/keys/client/call.go +++ b/tm2/pkg/crypto/keys/client/call.go @@ -22,7 +22,7 @@ type callCfg struct { args commands.StringArr } -func newCallCmd(rootCfg *makeTxCfg, io *commands.IO) *commands.Command { +func newCallCmd(rootCfg *makeTxCfg, io commands.IO) *commands.Command { cfg := &callCfg{ rootCfg: rootCfg, } @@ -69,7 +69,7 @@ func (c *callCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execCall(cfg *callCfg, args []string, io *commands.IO) error { +func execCall(cfg *callCfg, args []string, io commands.IO) error { if cfg.pkgPath == "" { return errors.New("pkgpath not specified") } diff --git a/tm2/pkg/crypto/keys/client/delete.go b/tm2/pkg/crypto/keys/client/delete.go index e22ac30988c..cf65b8fc60a 100644 --- a/tm2/pkg/crypto/keys/client/delete.go +++ b/tm2/pkg/crypto/keys/client/delete.go @@ -16,7 +16,7 @@ type deleteCfg struct { force bool } -func newDeleteCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newDeleteCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { cfg := &deleteCfg{ rootCfg: rootCfg, } @@ -50,7 +50,7 @@ func (c *deleteCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execDelete(cfg *deleteCfg, args []string, io *commands.IO) error { +func execDelete(cfg *deleteCfg, args []string, io commands.IO) error { if len(args) != 1 { return flag.ErrHelp } @@ -101,7 +101,7 @@ func execDelete(cfg *deleteCfg, args []string, io *commands.IO) error { return nil } -func confirmDeletion(io *commands.IO) error { +func confirmDeletion(io commands.IO) error { answer, err := io.GetConfirmation("Key reference will be deleted. Continue?") if err != nil { return err diff --git a/tm2/pkg/crypto/keys/client/export.go b/tm2/pkg/crypto/keys/client/export.go index 6eff8aa97b3..c3921c31fd7 100644 --- a/tm2/pkg/crypto/keys/client/export.go +++ b/tm2/pkg/crypto/keys/client/export.go @@ -18,7 +18,7 @@ type exportCfg struct { unsafe bool } -func newExportCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newExportCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { cfg := &exportCfg{ rootCfg: rootCfg, } @@ -59,7 +59,7 @@ func (c *exportCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execExport(cfg *exportCfg, io *commands.IO) error { +func execExport(cfg *exportCfg, io commands.IO) error { // Create a new instance of the key-base kb, err := keys.NewKeyBaseFromDir(cfg.rootCfg.Home) if err != nil { diff --git a/tm2/pkg/crypto/keys/client/export_test.go b/tm2/pkg/crypto/keys/client/export_test.go index a5f1ec8f48e..0f4c5311dfa 100644 --- a/tm2/pkg/crypto/keys/client/export_test.go +++ b/tm2/pkg/crypto/keys/client/export_test.go @@ -44,7 +44,7 @@ func addRandomKeyToKeybase( encryptPassword string, ) (keys.Info, error) { // Generate a random mnemonic - mnemonic, err := generateMnemonic(mnemonicEntropySize) + mnemonic, err := GenerateMnemonic(mnemonicEntropySize) if err != nil { return nil, fmt.Errorf( "unable to generate a mnemonic phrase, %w", @@ -64,11 +64,9 @@ func addRandomKeyToKeybase( } type testCmdKeyOptsBase struct { - kbHome string - keyName string - decryptPassword string - encryptPassword string - unsafe bool + kbHome string + keyName string + unsafe bool } type testExportKeyOpts struct { diff --git a/tm2/pkg/crypto/keys/client/generate.go b/tm2/pkg/crypto/keys/client/generate.go index d209bd70bd3..04a0ea8947f 100644 --- a/tm2/pkg/crypto/keys/client/generate.go +++ b/tm2/pkg/crypto/keys/client/generate.go @@ -16,7 +16,7 @@ type generateCfg struct { customEntropy bool } -func newGenerateCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newGenerateCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { cfg := &generateCfg{ rootCfg: rootCfg, } @@ -43,7 +43,7 @@ func (c *generateCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execGenerate(cfg *generateCfg, args []string, io *commands.IO) error { +func execGenerate(cfg *generateCfg, args []string, io commands.IO) error { customEntropy := cfg.customEntropy if len(args) != 0 { diff --git a/tm2/pkg/crypto/keys/client/helper.go b/tm2/pkg/crypto/keys/client/helper.go index 42a936910f7..525ad9071f8 100644 --- a/tm2/pkg/crypto/keys/client/helper.go +++ b/tm2/pkg/crypto/keys/client/helper.go @@ -2,9 +2,9 @@ package client import "github.com/gnolang/gno/tm2/pkg/crypto/bip39" -// generateMnemonic generates a new BIP39 mnemonic using the +// GenerateMnemonic generates a new BIP39 mnemonic using the // provided entropy size -func generateMnemonic(entropySize int) (string, error) { +func GenerateMnemonic(entropySize int) (string, error) { // Generate the entropy seed entropySeed, err := bip39.NewEntropy(entropySize) if err != nil { diff --git a/tm2/pkg/crypto/keys/client/import.go b/tm2/pkg/crypto/keys/client/import.go index e1d8af55861..e4f20ff6402 100644 --- a/tm2/pkg/crypto/keys/client/import.go +++ b/tm2/pkg/crypto/keys/client/import.go @@ -18,7 +18,7 @@ type importCfg struct { unsafe bool } -func newImportCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newImportCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { cfg := &importCfg{ rootCfg: rootCfg, } @@ -59,7 +59,7 @@ func (c *importCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execImport(cfg *importCfg, io *commands.IO) error { +func execImport(cfg *importCfg, io commands.IO) error { // Create a new instance of the key-base kb, err := keys.NewKeyBaseFromDir(cfg.rootCfg.Home) if err != nil { diff --git a/tm2/pkg/crypto/keys/client/list.go b/tm2/pkg/crypto/keys/client/list.go index cb86feb2395..bdee6b3bbe9 100644 --- a/tm2/pkg/crypto/keys/client/list.go +++ b/tm2/pkg/crypto/keys/client/list.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto/keys" ) -func newListCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newListCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { return commands.NewCommand( commands.Metadata{ Name: "list", @@ -22,7 +22,7 @@ func newListCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { ) } -func execList(cfg *baseCfg, args []string, io *commands.IO) error { +func execList(cfg *baseCfg, args []string, io commands.IO) error { if len(args) != 0 { return flag.ErrHelp } @@ -40,7 +40,7 @@ func execList(cfg *baseCfg, args []string, io *commands.IO) error { return err } -func printInfos(infos []keys.Info, io *commands.IO) { +func printInfos(infos []keys.Info, io commands.IO) { for i, info := range infos { keyname := info.GetName() keytype := info.GetType() diff --git a/tm2/pkg/crypto/keys/client/maketx.go b/tm2/pkg/crypto/keys/client/maketx.go index 36214a5a983..e3fe89b930d 100644 --- a/tm2/pkg/crypto/keys/client/maketx.go +++ b/tm2/pkg/crypto/keys/client/maketx.go @@ -17,7 +17,7 @@ type makeTxCfg struct { chainID string } -func newMakeTxCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newMakeTxCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { cfg := &makeTxCfg{ rootCfg: rootCfg, } diff --git a/tm2/pkg/crypto/keys/client/query.go b/tm2/pkg/crypto/keys/client/query.go index 8cc5757aba7..746048e772c 100644 --- a/tm2/pkg/crypto/keys/client/query.go +++ b/tm2/pkg/crypto/keys/client/query.go @@ -21,7 +21,7 @@ type queryCfg struct { path string } -func newQueryCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newQueryCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { cfg := &queryCfg{ rootCfg: rootCfg, } @@ -62,7 +62,7 @@ func (c *queryCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execQuery(cfg *queryCfg, args []string, io *commands.IO) error { +func execQuery(cfg *queryCfg, args []string, io commands.IO) error { if len(args) != 1 { return flag.ErrHelp } diff --git a/tm2/pkg/crypto/keys/client/root.go b/tm2/pkg/crypto/keys/client/root.go index 550dd408b77..61fd7d077b5 100644 --- a/tm2/pkg/crypto/keys/client/root.go +++ b/tm2/pkg/crypto/keys/client/root.go @@ -18,7 +18,7 @@ type baseCfg struct { BaseOptions } -func NewRootCmd(io *commands.IO) *commands.Command { +func NewRootCmd(io commands.IO) *commands.Command { cfg := &baseCfg{} cmd := commands.NewCommand( diff --git a/tm2/pkg/crypto/keys/client/send.go b/tm2/pkg/crypto/keys/client/send.go index 6d19ffcb393..a5098aea08c 100644 --- a/tm2/pkg/crypto/keys/client/send.go +++ b/tm2/pkg/crypto/keys/client/send.go @@ -21,7 +21,7 @@ type sendCfg struct { to string } -func newSendCmd(rootCfg *makeTxCfg, io *commands.IO) *commands.Command { +func newSendCmd(rootCfg *makeTxCfg, io commands.IO) *commands.Command { cfg := &sendCfg{ rootCfg: rootCfg, } @@ -55,7 +55,7 @@ func (c *sendCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execSend(cfg *sendCfg, args []string, io *commands.IO) error { +func execSend(cfg *sendCfg, args []string, io commands.IO) error { if len(args) != 1 { return flag.ErrHelp } diff --git a/tm2/pkg/crypto/keys/client/sign.go b/tm2/pkg/crypto/keys/client/sign.go index 761e0d7a563..f8fcc02fdde 100644 --- a/tm2/pkg/crypto/keys/client/sign.go +++ b/tm2/pkg/crypto/keys/client/sign.go @@ -28,7 +28,7 @@ type signCfg struct { pass string } -func newSignCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newSignCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { cfg := &signCfg{ rootCfg: rootCfg, } @@ -83,7 +83,7 @@ func (c *signCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execSign(cfg *signCfg, args []string, io *commands.IO) error { +func execSign(cfg *signCfg, args []string, io commands.IO) error { var err error if len(args) != 1 { diff --git a/tm2/pkg/crypto/keys/client/verify.go b/tm2/pkg/crypto/keys/client/verify.go index bb486c1a8fa..fff2fcd852f 100644 --- a/tm2/pkg/crypto/keys/client/verify.go +++ b/tm2/pkg/crypto/keys/client/verify.go @@ -16,7 +16,7 @@ type verifyCfg struct { docPath string } -func newVerifyCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { +func newVerifyCmd(rootCfg *baseCfg, io commands.IO) *commands.Command { cfg := &verifyCfg{ rootCfg: rootCfg, } @@ -43,7 +43,7 @@ func (c *verifyCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execVerify(cfg *verifyCfg, args []string, io *commands.IO) error { +func execVerify(cfg *verifyCfg, args []string, io commands.IO) error { var ( kb keys.Keybase err error diff --git a/tm2/pkg/crypto/keys/keybase.go b/tm2/pkg/crypto/keys/keybase.go index 16b3631d188..31b0012c433 100644 --- a/tm2/pkg/crypto/keys/keybase.go +++ b/tm2/pkg/crypto/keys/keybase.go @@ -46,14 +46,6 @@ const ( infoSuffix = "info" ) -const ( - // used for deriving seed from mnemonic - DefaultBIP39Passphrase = "" - - // bits of entropy to draw when creating a mnemonic - defaultEntropySize = 256 -) - var ( // ErrUnsupportedSigningAlgo is raised when the caller tries to use a // different signing scheme than secp256k1. @@ -168,14 +160,32 @@ func (kb dbKeybase) List() ([]Info, error) { return res, nil } +// HasByNameOrAddress checks if a key with the name or bech32 string address is in the keybase. +func (kb dbKeybase) HasByNameOrAddress(nameOrBech32 string) (bool, error) { + address, err := crypto.AddressFromBech32(nameOrBech32) + if err != nil { + return kb.HasByName(nameOrBech32) + } + return kb.HasByAddress(address) +} + +// HasByName checks if a key with the name is in the keybase. +func (kb dbKeybase) HasByName(name string) (bool, error) { + return kb.db.Has(infoKey(name)), nil +} + +// HasByAddress checks if a key with the address is in the keybase. +func (kb dbKeybase) HasByAddress(address crypto.Address) (bool, error) { + return kb.db.Has(addrKey(address)), nil +} + // Get returns the public information about one key. func (kb dbKeybase) GetByNameOrAddress(nameOrBech32 string) (Info, error) { addr, err := crypto.AddressFromBech32(nameOrBech32) if err != nil { return kb.GetByName(nameOrBech32) - } else { - return kb.GetByAddress(addr) } + return kb.GetByAddress(addr) } func (kb dbKeybase) GetByName(name string) (Info, error) { @@ -189,7 +199,7 @@ func (kb dbKeybase) GetByName(name string) (Info, error) { func (kb dbKeybase) GetByAddress(address crypto.Address) (Info, error) { ik := kb.db.Get(addrKey(address)) if len(ik) == 0 { - return nil, fmt.Errorf("key with address %s not found", address) + return nil, keyerror.NewErrKeyNotFound(fmt.Sprintf("key with address %s not found", address)) } bs := kb.db.Get(ik) return readInfo(bs) diff --git a/tm2/pkg/crypto/keys/keybase_test.go b/tm2/pkg/crypto/keys/keybase_test.go index 987a271881b..d7660ac38f1 100644 --- a/tm2/pkg/crypto/keys/keybase_test.go +++ b/tm2/pkg/crypto/keys/keybase_test.go @@ -9,9 +9,12 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" + "github.com/gnolang/gno/tm2/pkg/crypto/keys/keyerror" ) func TestCreateAccountInvalidMnemonic(t *testing.T) { + t.Parallel() + kb := NewInMemory() _, err := kb.CreateAccount( "some_account", @@ -22,6 +25,8 @@ func TestCreateAccountInvalidMnemonic(t *testing.T) { } func TestCreateLedgerUnsupportedAlgo(t *testing.T) { + t.Parallel() + kb := NewInMemory() _, err := kb.CreateLedger("some_account", Ed25519, "cosmos", 0, 1) assert.Error(t, err) @@ -29,6 +34,8 @@ func TestCreateLedgerUnsupportedAlgo(t *testing.T) { } func TestCreateLedger(t *testing.T) { + t.Parallel() + kb := NewInMemory() // test_cover and test_unit will result in different answers @@ -65,6 +72,8 @@ func TestCreateLedger(t *testing.T) { // TestKeyManagement makes sure we can manipulate these keys well func TestKeyManagement(t *testing.T) { + t.Parallel() + // make the storage with reasonable defaults cstore := NewInMemory() @@ -80,8 +89,9 @@ func TestKeyManagement(t *testing.T) { assert.Empty(t, l) // create some keys - _, err = cstore.GetByName(n1) - require.Error(t, err) + has, err := cstore.HasByName(n1) + require.NoError(t, err) + require.False(t, has) i, err := cstore.CreateAccount(n1, mn1, bip39Passphrase, p1, 0, 0) require.NoError(t, err) require.Equal(t, n1, i.GetName()) @@ -91,14 +101,21 @@ func TestKeyManagement(t *testing.T) { // we can get these keys i2, err := cstore.GetByName(n2) require.NoError(t, err) - _, err = cstore.GetByName(n3) - require.NotNil(t, err) - _, err = cstore.GetByAddress(toAddr(i2)) + has, err = cstore.HasByName(n3) + require.NoError(t, err) + require.False(t, has) + has, err = cstore.HasByAddress(toAddr(i2)) + require.NoError(t, err) + require.True(t, has) + // Also check with HasByNameOrAddress + has, err = cstore.HasByNameOrAddress(crypto.AddressToBech32(toAddr(i2))) require.NoError(t, err) + require.True(t, has) addr, err := crypto.AddressFromBech32("g1frtkxv37nq7arvyz5p0mtjqq7hwuvd4dnt892p") require.NoError(t, err) _, err = cstore.GetByAddress(addr) require.NotNil(t, err) + require.True(t, keyerror.IsErrKeyNotFound(err)) // list shows them in order keyS, err := cstore.List() @@ -117,8 +134,9 @@ func TestKeyManagement(t *testing.T) { keyS, err = cstore.List() require.NoError(t, err) require.Equal(t, 1, len(keyS)) - _, err = cstore.GetByName(n1) - require.Error(t, err) + has, err = cstore.HasByName(n1) + require.NoError(t, err) + require.False(t, has) // create an offline key o1 := "offline" @@ -147,6 +165,8 @@ func TestKeyManagement(t *testing.T) { // TestSignVerify does some detailed checks on how we sign and validate // signatures func TestSignVerify(t *testing.T) { + t.Parallel() + cstore := NewInMemory() n1, n2, n3 := "some dude", "a dudette", "dude-ish" @@ -233,6 +253,8 @@ func assertPassword(t *testing.T, cstore Keybase, name, pass, badpass string) { // TestExportImport tests exporting and importing func TestExportImport(t *testing.T) { + t.Parallel() + // make the storage with reasonable defaults cstore := NewInMemory() @@ -263,6 +285,8 @@ func TestExportImport(t *testing.T) { } func TestExportImportPubKey(t *testing.T) { + t.Parallel() + // make the storage with reasonable defaults cstore := NewInMemory() @@ -304,6 +328,8 @@ func TestExportImportPubKey(t *testing.T) { // TestAdvancedKeyManagement verifies update, import, export functionality func TestAdvancedKeyManagement(t *testing.T) { + t.Parallel() + // make the storage with reasonable defaults cstore := NewInMemory() @@ -352,6 +378,8 @@ func TestAdvancedKeyManagement(t *testing.T) { // TestSeedPhrase verifies restoring from a seed phrase func TestSeedPhrase(t *testing.T) { + t.Parallel() + // make the storage with reasonable defaults cstore := NewInMemory() @@ -368,8 +396,9 @@ func TestSeedPhrase(t *testing.T) { // now, let us delete this key err = cstore.Delete(n1, p1, false) require.Nil(t, err, "%+v", err) - _, err = cstore.GetByName(n1) - require.NotNil(t, err) + has, err := cstore.HasByName(n1) + require.NoError(t, err) + require.False(t, has) } func ExampleNew() { diff --git a/tm2/pkg/crypto/keys/keyerror/errors.go b/tm2/pkg/crypto/keys/keyerror/errors.go index 93eb63d2bf3..f7dc97e972d 100644 --- a/tm2/pkg/crypto/keys/keyerror/errors.go +++ b/tm2/pkg/crypto/keys/keyerror/errors.go @@ -1,6 +1,7 @@ package keyerror import ( + "errors" "fmt" ) @@ -40,7 +41,8 @@ func IsErrKeyNotFound(err error) bool { if err == nil { return false } - if keyErr, ok := err.(keybaseError); ok { + var keyErr keybaseError + if errors.As(err, &keyErr) { if keyErr.Code() == codeKeyNotFound { return true } @@ -72,7 +74,8 @@ func IsErrWrongPassword(err error) bool { if err == nil { return false } - if keyErr, ok := err.(keybaseError); ok { + var keyErr keybaseError + if errors.As(err, &keyErr) { if keyErr.Code() == codeWrongPassword { return true } diff --git a/tm2/pkg/crypto/keys/lazy_keybase.go b/tm2/pkg/crypto/keys/lazy_keybase.go index f7f9e229980..62e88d9a8e2 100644 --- a/tm2/pkg/crypto/keys/lazy_keybase.go +++ b/tm2/pkg/crypto/keys/lazy_keybase.go @@ -37,6 +37,36 @@ func (lkb lazyKeybase) List() ([]Info, error) { return NewDBKeybase(db).List() } +func (lkb lazyKeybase) HasByNameOrAddress(nameOrBech32 string) (bool, error) { + db, err := db.NewDB(lkb.name, dbBackend, lkb.dir) + if err != nil { + return false, err + } + defer db.Close() + + return NewDBKeybase(db).HasByNameOrAddress(nameOrBech32) +} + +func (lkb lazyKeybase) HasByName(name string) (bool, error) { + db, err := db.NewDB(lkb.name, dbBackend, lkb.dir) + if err != nil { + return false, err + } + defer db.Close() + + return NewDBKeybase(db).HasByName(name) +} + +func (lkb lazyKeybase) HasByAddress(address crypto.Address) (bool, error) { + db, err := db.NewDB(lkb.name, dbBackend, lkb.dir) + if err != nil { + return false, err + } + defer db.Close() + + return NewDBKeybase(db).HasByAddress(address) +} + func (lkb lazyKeybase) GetByNameOrAddress(nameOrBech32 string) (Info, error) { db, err := db.NewDB(lkb.name, dbBackend, lkb.dir) if err != nil { diff --git a/tm2/pkg/crypto/keys/types.go b/tm2/pkg/crypto/keys/types.go index bba3a917b69..c5d33023a0a 100644 --- a/tm2/pkg/crypto/keys/types.go +++ b/tm2/pkg/crypto/keys/types.go @@ -13,6 +13,9 @@ import ( type Keybase interface { // CRUD on the keystore List() ([]Info, error) + HasByNameOrAddress(nameOrBech32 string) (bool, error) + HasByName(name string) (bool, error) + HasByAddress(address crypto.Address) (bool, error) GetByNameOrAddress(nameOrBech32 string) (Info, error) GetByName(name string) (Info, error) GetByAddress(address crypto.Address) (Info, error) diff --git a/tm2/pkg/crypto/keys/types_test.go b/tm2/pkg/crypto/keys/types_test.go index a0591819a88..2a2dd2c82f6 100644 --- a/tm2/pkg/crypto/keys/types_test.go +++ b/tm2/pkg/crypto/keys/types_test.go @@ -11,6 +11,8 @@ import ( ) func Test_writeReadLedgerInfo(t *testing.T) { + t.Parallel() + var tmpKey secp256k1.PubKeySecp256k1 bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A") copy(tmpKey[:], bz) diff --git a/tm2/pkg/crypto/merkle/proof_key_path_test.go b/tm2/pkg/crypto/merkle/proof_key_path_test.go index 22e3e21ca3d..669e8c5630c 100644 --- a/tm2/pkg/crypto/merkle/proof_key_path_test.go +++ b/tm2/pkg/crypto/merkle/proof_key_path_test.go @@ -10,6 +10,8 @@ import ( ) func TestKeyPath(t *testing.T) { + t.Parallel() + var path KeyPath keys := make([][]byte, 10) alphanum := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" diff --git a/tm2/pkg/crypto/merkle/proof_test.go b/tm2/pkg/crypto/merkle/proof_test.go index 676e281ac60..0424ea351dd 100644 --- a/tm2/pkg/crypto/merkle/proof_test.go +++ b/tm2/pkg/crypto/merkle/proof_test.go @@ -26,19 +26,6 @@ func NewDominoOp(key, input, output string) DominoOp { } } -//nolint:unused -func DominoOpDecoder(pop ProofOp) (ProofOperator, error) { - if pop.Type != ProofOpDomino { - panic("unexpected proof op type") - } - var op DominoOp // a bit strange as we'll discard this, but it works. - err := amino.UnmarshalSized(pop.Data, &op) - if err != nil { - return nil, errors.Wrap(err, "decoding ProofOp.Data into SimpleValueOp") - } - return NewDominoOp(string(pop.Key), op.Input, op.Output), nil -} - func (dop DominoOp) ProofOp() ProofOp { bz := amino.MustMarshalSized(dop) return ProofOp{ @@ -63,9 +50,11 @@ func (dop DominoOp) GetKey() []byte { return []byte(dop.key) } -//---------------------------------------- +// ---------------------------------------- func TestProofOperators(t *testing.T) { + t.Parallel() + var err error // ProofRuntime setup diff --git a/tm2/pkg/crypto/merkle/rfc6962_test.go b/tm2/pkg/crypto/merkle/rfc6962_test.go index 917b09015b0..57f72cea311 100644 --- a/tm2/pkg/crypto/merkle/rfc6962_test.go +++ b/tm2/pkg/crypto/merkle/rfc6962_test.go @@ -24,6 +24,8 @@ import ( ) func TestRFC6962Hasher(t *testing.T) { + t.Parallel() + _, leafHashTrail := trailsFromByteSlices([][]byte{[]byte("L123456")}) leafHash := leafHashTrail.Hash _, leafHashTrail = trailsFromByteSlices([][]byte{{}}) @@ -58,6 +60,8 @@ func TestRFC6962Hasher(t *testing.T) { } { tc := tc t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + wantBytes, err := hex.DecodeString(tc.want) if err != nil { t.Fatalf("hex.DecodeString(%x): %v", tc.want, err) @@ -70,6 +74,8 @@ func TestRFC6962Hasher(t *testing.T) { } func TestRFC6962HasherCollisions(t *testing.T) { + t.Parallel() + // Check that different leaves have different hashes. leaf1, leaf2 := []byte("Hello"), []byte("World") _, leafHashTrail := trailsFromByteSlices([][]byte{leaf1}) diff --git a/tm2/pkg/crypto/merkle/simple_map_test.go b/tm2/pkg/crypto/merkle/simple_map_test.go index 366d9f39099..6df8e0b967e 100644 --- a/tm2/pkg/crypto/merkle/simple_map_test.go +++ b/tm2/pkg/crypto/merkle/simple_map_test.go @@ -8,6 +8,8 @@ import ( ) func TestSimpleMap(t *testing.T) { + t.Parallel() + tests := []struct { keys []string values []string // each string gets converted to []byte in test diff --git a/tm2/pkg/crypto/merkle/simple_proof_test.go b/tm2/pkg/crypto/merkle/simple_proof_test.go index 1a517905b5e..88677e3fb0e 100644 --- a/tm2/pkg/crypto/merkle/simple_proof_test.go +++ b/tm2/pkg/crypto/merkle/simple_proof_test.go @@ -7,6 +7,8 @@ import ( ) func TestSimpleProofValidateBasic(t *testing.T) { + t.Parallel() + testCases := []struct { testName string malleateProof func(*SimpleProof) @@ -23,6 +25,8 @@ func TestSimpleProofValidateBasic(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + _, proofs := SimpleProofsFromByteSlices([][]byte{ []byte("apple"), []byte("watermelon"), diff --git a/tm2/pkg/crypto/merkle/simple_tree_test.go b/tm2/pkg/crypto/merkle/simple_tree_test.go index 8690bae6ac3..209df75b1b5 100644 --- a/tm2/pkg/crypto/merkle/simple_tree_test.go +++ b/tm2/pkg/crypto/merkle/simple_tree_test.go @@ -17,6 +17,8 @@ func (tI testItem) Hash() []byte { } func TestSimpleProof(t *testing.T) { + t.Parallel() + total := 100 items := make([][]byte, total) @@ -69,6 +71,8 @@ func TestSimpleProof(t *testing.T) { } func TestSimpleHashAlternatives(t *testing.T) { + t.Parallel() + total := 100 items := make([][]byte, total) @@ -104,6 +108,8 @@ func BenchmarkSimpleHashAlternatives(b *testing.B) { } func Test_getSplitPoint(t *testing.T) { + t.Parallel() + tests := []struct { length int want int diff --git a/tm2/pkg/crypto/mock/mock_test.go b/tm2/pkg/crypto/mock/mock_test.go index 2e5f1d776bd..3d7ac571faa 100644 --- a/tm2/pkg/crypto/mock/mock_test.go +++ b/tm2/pkg/crypto/mock/mock_test.go @@ -10,6 +10,8 @@ import ( ) func TestSignAndValidateMock(t *testing.T) { + t.Parallel() + privKey := mock.PrivKeyMock([]byte{0x01}) pubKey := privKey.PubKey() diff --git a/tm2/pkg/crypto/multisig/bitarray/compact_bit_array_test.go b/tm2/pkg/crypto/multisig/bitarray/compact_bit_array_test.go index ecbfb64005d..c41e8e3c87b 100644 --- a/tm2/pkg/crypto/multisig/bitarray/compact_bit_array_test.go +++ b/tm2/pkg/crypto/multisig/bitarray/compact_bit_array_test.go @@ -28,6 +28,8 @@ func randCompactBitArray(bits int) (*CompactBitArray, []byte) { } func TestNewBitArrayNeverCrashesOnNegatives(t *testing.T) { + t.Parallel() + bitList := []int{-127, -128, -1 << 31} for _, bits := range bitList { bA := NewCompactBitArray(bits) @@ -36,6 +38,8 @@ func TestNewBitArrayNeverCrashesOnNegatives(t *testing.T) { } func TestJSONMarshalUnmarshal(t *testing.T) { + t.Parallel() + bA1 := NewCompactBitArray(0) bA2 := NewCompactBitArray(1) @@ -73,6 +77,8 @@ func TestJSONMarshalUnmarshal(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.bA.String(), func(t *testing.T) { + t.Parallel() + bz, err := json.Marshal(tc.bA) require.NoError(t, err) @@ -96,6 +102,8 @@ func TestJSONMarshalUnmarshal(t *testing.T) { } func TestCompactMarshalUnmarshal(t *testing.T) { + t.Parallel() + bA1 := NewCompactBitArray(0) bA2 := NewCompactBitArray(1) @@ -133,6 +141,8 @@ func TestCompactMarshalUnmarshal(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.bA.String(), func(t *testing.T) { + t.Parallel() + bz := tc.bA.CompactMarshal() assert.Equal(t, tc.marshalledBA, bz) @@ -153,6 +163,8 @@ func TestCompactMarshalUnmarshal(t *testing.T) { } func TestCompactBitArrayNumOfTrueBitsBefore(t *testing.T) { + t.Parallel() + testCases := []struct { marshalledBA string bAIndex []int @@ -169,6 +181,8 @@ func TestCompactBitArrayNumOfTrueBitsBefore(t *testing.T) { tc := tc tcIndex := tcIndex t.Run(tc.marshalledBA, func(t *testing.T) { + t.Parallel() + var bA *CompactBitArray err := json.Unmarshal([]byte(tc.marshalledBA), &bA) require.NoError(t, err) @@ -181,6 +195,8 @@ func TestCompactBitArrayNumOfTrueBitsBefore(t *testing.T) { } func TestCompactBitArrayGetSetIndex(t *testing.T) { + t.Parallel() + r := rand.New(rand.NewSource(100)) numTests := 10 numBitsPerArr := 100 diff --git a/tm2/pkg/crypto/multisig/threshold_pubkey_test.go b/tm2/pkg/crypto/multisig/threshold_pubkey_test.go index 9195344f423..548e8df0a82 100644 --- a/tm2/pkg/crypto/multisig/threshold_pubkey_test.go +++ b/tm2/pkg/crypto/multisig/threshold_pubkey_test.go @@ -15,6 +15,8 @@ import ( // This tests multisig functionality, but it expects the first k signatures to be valid // TODO: Adapt it to give more flexibility about first k signatures being valid func TestThresholdMultisigValidCases(t *testing.T) { + t.Parallel() + pkSet1, sigSet1 := generatePubKeysAndSignatures(5, []byte{1, 2, 3, 4}) cases := []struct { msg []byte @@ -105,6 +107,8 @@ func TestThresholdMultisigValidCases(t *testing.T) { // TODO: Fully replace this test with table driven tests func TestThresholdMultisigDuplicateSignatures(t *testing.T) { + t.Parallel() + msg := []byte{1, 2, 3, 4, 5} pubkeys, sigs := generatePubKeysAndSignatures(5, msg) multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) @@ -118,6 +122,8 @@ func TestThresholdMultisigDuplicateSignatures(t *testing.T) { // TODO: Fully replace this test with table driven tests func TestMultiSigPubKeyEquality(t *testing.T) { + t.Parallel() + msg := []byte{1, 2, 3, 4} pubkeys, _ := generatePubKeysAndSignatures(5, msg) multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) @@ -135,6 +141,8 @@ func TestMultiSigPubKeyEquality(t *testing.T) { } func TestAddress(t *testing.T) { + t.Parallel() + msg := []byte{1, 2, 3, 4} pubkeys, _ := generatePubKeysAndSignatures(5, msg) multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) @@ -142,6 +150,8 @@ func TestAddress(t *testing.T) { } func TestPubKeyMultisigThresholdAminoToIface(t *testing.T) { + t.Parallel() + msg := []byte{1, 2, 3, 4} pubkeys, _ := generatePubKeysAndSignatures(5, msg) multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) diff --git a/tm2/pkg/crypto/random_test.go b/tm2/pkg/crypto/random_test.go index aab505cb61c..dd405c06859 100644 --- a/tm2/pkg/crypto/random_test.go +++ b/tm2/pkg/crypto/random_test.go @@ -11,6 +11,8 @@ import ( // the purpose of this test is primarily to ensure that the randomness // generation won't error. func TestRandomConsistency(t *testing.T) { + t.Parallel() + x1 := crypto.CRandBytes(256) x2 := crypto.CRandBytes(256) x3 := crypto.CRandBytes(256) diff --git a/tm2/pkg/crypto/secp256k1/secp256k1_cgo_test.go b/tm2/pkg/crypto/secp256k1/secp256k1_cgo_test.go index e5b584707e5..d0070ad9bbe 100644 --- a/tm2/pkg/crypto/secp256k1/secp256k1_cgo_test.go +++ b/tm2/pkg/crypto/secp256k1/secp256k1_cgo_test.go @@ -10,6 +10,8 @@ import ( ) func TestPrivKeySecp256k1SignVerify(t *testing.T) { + t.Parallel() + msg := []byte("A.1.2 ECC Key Pair Generation by Testing Candidates") priv := GenPrivKey() tests := []struct { @@ -23,6 +25,8 @@ func TestPrivKeySecp256k1SignVerify(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := tt.privKey.Sign(msg) if tt.wantSignErr { require.Error(t, err) diff --git a/tm2/pkg/crypto/secp256k1/secp256k1_internal_test.go b/tm2/pkg/crypto/secp256k1/secp256k1_internal_test.go index d680d24f6a9..d80bea83dba 100644 --- a/tm2/pkg/crypto/secp256k1/secp256k1_internal_test.go +++ b/tm2/pkg/crypto/secp256k1/secp256k1_internal_test.go @@ -11,6 +11,8 @@ import ( ) func Test_genPrivKey(t *testing.T) { + t.Parallel() + empty := make([]byte, 32) oneB := big.NewInt(1).Bytes() onePadded := make([]byte, 32) @@ -30,6 +32,8 @@ func Test_genPrivKey(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.shouldPanic { require.Panics(t, func() { genPrivKey(bytes.NewReader(tt.notSoRand)) diff --git a/tm2/pkg/crypto/secp256k1/secp256k1_nocgo_test.go b/tm2/pkg/crypto/secp256k1/secp256k1_nocgo_test.go index e75d5377670..aeaca9df47f 100644 --- a/tm2/pkg/crypto/secp256k1/secp256k1_nocgo_test.go +++ b/tm2/pkg/crypto/secp256k1/secp256k1_nocgo_test.go @@ -13,6 +13,8 @@ import ( // non-canonical signatures fail. // Note: run with CGO_ENABLED=0 or go test -tags !cgo. func TestSignatureVerificationAndRejectUpperS(t *testing.T) { + t.Parallel() + msg := []byte("We have lingered long enough on the shores of the cosmic ocean.") for i := 0; i < 500; i++ { priv := GenPrivKey() diff --git a/tm2/pkg/crypto/secp256k1/secp256k1_test.go b/tm2/pkg/crypto/secp256k1/secp256k1_test.go index 86aa058f95a..e04f622fdbb 100644 --- a/tm2/pkg/crypto/secp256k1/secp256k1_test.go +++ b/tm2/pkg/crypto/secp256k1/secp256k1_test.go @@ -30,6 +30,8 @@ var secpDataTable = []keyData{ } func TestPubKeySecp256k1Address(t *testing.T) { + t.Parallel() + for _, d := range secpDataTable { privB, _ := hex.DecodeString(d.priv) pubB, _ := hex.DecodeString(d.pub) @@ -50,6 +52,8 @@ func TestPubKeySecp256k1Address(t *testing.T) { } func TestSignAndValidateSecp256k1(t *testing.T) { + t.Parallel() + privKey := secp256k1.GenPrivKey() pubKey := privKey.PubKey() @@ -68,6 +72,8 @@ func TestSignAndValidateSecp256k1(t *testing.T) { // This test is intended to justify the removal of calls to the underlying library // in creating the privkey. func TestSecp256k1LoadPrivkeyAndSerializeIsIdentity(t *testing.T) { + t.Parallel() + numberOfTests := 256 for i := 0; i < numberOfTests; i++ { // Seed the test case with some random bytes @@ -87,6 +93,8 @@ func TestSecp256k1LoadPrivkeyAndSerializeIsIdentity(t *testing.T) { } func TestGenPrivKeySecp256k1(t *testing.T) { + t.Parallel() + // curve oder N N := underlyingSecp256k1.S256().N tests := []struct { @@ -102,6 +110,8 @@ func TestGenPrivKeySecp256k1(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + gotPrivKey := secp256k1.GenPrivKeySecp256k1(tt.secret) require.NotNil(t, gotPrivKey) // interpret as a big.Int and make sure it is a valid field element: diff --git a/tm2/pkg/crypto/tmhash/hash_test.go b/tm2/pkg/crypto/tmhash/hash_test.go index 4a129bb86ac..c69d86653b1 100644 --- a/tm2/pkg/crypto/tmhash/hash_test.go +++ b/tm2/pkg/crypto/tmhash/hash_test.go @@ -9,6 +9,8 @@ import ( ) func TestHash(t *testing.T) { + t.Parallel() + testVector := []byte("abc") hasher := tmhash.New() hasher.Write(testVector) @@ -25,6 +27,8 @@ func TestHash(t *testing.T) { } func TestHashTruncated(t *testing.T) { + t.Parallel() + testVector := []byte("abc") hasher := tmhash.NewTruncated() hasher.Write(testVector) diff --git a/tm2/pkg/crypto/util.go b/tm2/pkg/crypto/util.go deleted file mode 100644 index f017557f09b..00000000000 --- a/tm2/pkg/crypto/util.go +++ /dev/null @@ -1,7 +0,0 @@ -package crypto - -func cp(bz []byte) (ret []byte) { - ret = make([]byte, len(bz)) - copy(ret, bz) - return ret -} diff --git a/tm2/pkg/crypto/xchacha20poly1305/vector_test.go b/tm2/pkg/crypto/xchacha20poly1305/vector_test.go index 3001217f41e..7ed186de19d 100644 --- a/tm2/pkg/crypto/xchacha20poly1305/vector_test.go +++ b/tm2/pkg/crypto/xchacha20poly1305/vector_test.go @@ -19,6 +19,8 @@ func fromHex(bits string) []byte { } func TestHChaCha20(t *testing.T) { + t.Parallel() + for i, v := range hChaCha20Vectors { var key [32]byte var nonce [16]byte @@ -63,6 +65,8 @@ var hChaCha20Vectors = []struct { } func TestVectors(t *testing.T) { + t.Parallel() + for i, v := range vectors { if len(v.plaintext) == 0 { v.plaintext = make([]byte, len(v.ciphertext)) diff --git a/tm2/pkg/crypto/xchacha20poly1305/xchachapoly_test.go b/tm2/pkg/crypto/xchacha20poly1305/xchachapoly_test.go index 5b92a7607e7..ea7b0bee1a4 100644 --- a/tm2/pkg/crypto/xchacha20poly1305/xchachapoly_test.go +++ b/tm2/pkg/crypto/xchacha20poly1305/xchachapoly_test.go @@ -14,6 +14,8 @@ import ( // Use of this source code is governed by a BSD-style // license that can be found at the bottom of this file. func TestRandom(t *testing.T) { + t.Parallel() + // Some random tests to verify Open(Seal) == Plaintext for i := 0; i < 256; i++ { var nonce [24]byte diff --git a/tm2/pkg/crypto/xsalsa20symmetric/symmetric_test.go b/tm2/pkg/crypto/xsalsa20symmetric/symmetric_test.go index dea965a4356..7460add5185 100644 --- a/tm2/pkg/crypto/xsalsa20symmetric/symmetric_test.go +++ b/tm2/pkg/crypto/xsalsa20symmetric/symmetric_test.go @@ -11,6 +11,8 @@ import ( ) func TestSimple(t *testing.T) { + t.Parallel() + plaintext := []byte("sometext") secret := []byte("somesecretoflengththirtytwo===32") ciphertext := EncryptSymmetric(plaintext, secret) @@ -21,6 +23,8 @@ func TestSimple(t *testing.T) { } func TestSimpleWithKDF(t *testing.T) { + t.Parallel() + salt := []byte("1234567890123456") plaintext := []byte("sometext") secretPass := []byte("somesecret") diff --git a/tm2/pkg/db/backend_test.go b/tm2/pkg/db/backend_test.go index b6cd3f026aa..fd03629fd58 100644 --- a/tm2/pkg/db/backend_test.go +++ b/tm2/pkg/db/backend_test.go @@ -41,8 +41,12 @@ func testBackendGetSetDelete(t *testing.T, backend BackendType) { } func TestBackendsGetSetDelete(t *testing.T) { + t.Parallel() + for dbType := range backends { t.Run(string(dbType), func(t *testing.T) { + t.Parallel() + testBackendGetSetDelete(t, dbType) }) } @@ -59,6 +63,8 @@ func withDB(t *testing.T, creator dbCreator, fn func(DB)) { } func TestBackendsNilKeys(t *testing.T) { + t.Parallel() + // Test all backends. for dbType, creator := range backends { withDB(t, creator, func(db DB) { @@ -137,6 +143,8 @@ func TestBackendsNilKeys(t *testing.T) { } func TestGoLevelDBBackend(t *testing.T) { + t.Parallel() + name := fmt.Sprintf("test_%x", randStr(12)) db, err := NewDB(name, GoLevelDBBackend, t.TempDir()) require.NoError(t, err) @@ -146,9 +154,11 @@ func TestGoLevelDBBackend(t *testing.T) { } func TestDBIterator(t *testing.T) { + t.Parallel() + for dbType := range backends { t.Run(fmt.Sprintf("%v", dbType), func(t *testing.T) { - t.Helper() + t.Parallel() testDBIterator(t, dbType) }) diff --git a/tm2/pkg/db/boltdb_test.go b/tm2/pkg/db/boltdb_test.go index 031023f275a..57091fb53a2 100644 --- a/tm2/pkg/db/boltdb_test.go +++ b/tm2/pkg/db/boltdb_test.go @@ -10,6 +10,8 @@ import ( ) func TestBoltDBNewBoltDB(t *testing.T) { + t.Parallel() + name := fmt.Sprintf("test_%x", randStr(12)) db, err := NewBoltDB(name, t.TempDir()) diff --git a/tm2/pkg/db/c_level_db_test.go b/tm2/pkg/db/c_level_db_test.go index 2bdffd14a83..7044ea0f4de 100644 --- a/tm2/pkg/db/c_level_db_test.go +++ b/tm2/pkg/db/c_level_db_test.go @@ -88,6 +88,8 @@ func bytes2Int64(buf []byte) int64 { */ func TestCLevelDBBackend(t *testing.T) { + t.Parallel() + name := fmt.Sprintf("test_%x", randStr(12)) // Can't use "" (current directory) or "./" here because levigo.Open returns: // "Error initializing DB: IO error: test_XXX.db: Invalid argument" @@ -99,6 +101,8 @@ func TestCLevelDBBackend(t *testing.T) { } func TestCLevelDBStats(t *testing.T) { + t.Parallel() + name := fmt.Sprintf("test_%x", randStr(12)) db, err := NewDB(name, CLevelDBBackend, t.TempDir()) require.NoError(t, err) diff --git a/tm2/pkg/db/db_test.go b/tm2/pkg/db/db_test.go index 3634fb4bf03..62231645613 100644 --- a/tm2/pkg/db/db_test.go +++ b/tm2/pkg/db/db_test.go @@ -8,8 +8,12 @@ import ( ) func TestDBIteratorSingleKey(t *testing.T) { + t.Parallel() + for backend := range backends { t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) db.SetSync(bz("1"), bz("value_1")) @@ -27,8 +31,12 @@ func TestDBIteratorSingleKey(t *testing.T) { } func TestDBIteratorTwoKeys(t *testing.T) { + t.Parallel() + for backend := range backends { t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) db.SetSync(bz("1"), bz("value_1")) @@ -54,8 +62,12 @@ func TestDBIteratorTwoKeys(t *testing.T) { } func TestDBIteratorMany(t *testing.T) { + t.Parallel() + for backend := range backends { t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) keys := make([][]byte, 100) @@ -78,8 +90,12 @@ func TestDBIteratorMany(t *testing.T) { } func TestDBIteratorEmpty(t *testing.T) { + t.Parallel() + for backend := range backends { t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) itr := db.Iterator(nil, nil) @@ -90,8 +106,12 @@ func TestDBIteratorEmpty(t *testing.T) { } func TestDBIteratorEmptyBeginAfter(t *testing.T) { + t.Parallel() + for backend := range backends { t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) itr := db.Iterator(bz("1"), nil) @@ -102,8 +122,12 @@ func TestDBIteratorEmptyBeginAfter(t *testing.T) { } func TestDBIteratorNonemptyBeginAfter(t *testing.T) { + t.Parallel() + for backend := range backends { t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) db.SetSync(bz("1"), bz("value_1")) @@ -115,6 +139,8 @@ func TestDBIteratorNonemptyBeginAfter(t *testing.T) { } func TestDBBatchWrite(t *testing.T) { + t.Parallel() + testCases := []struct { modify func(batch Batch) calls map[string]int diff --git a/tm2/pkg/db/fsdb.go b/tm2/pkg/db/fsdb.go index aa8cea83889..e11cd6d4ce5 100644 --- a/tm2/pkg/db/fsdb.go +++ b/tm2/pkg/db/fsdb.go @@ -2,7 +2,7 @@ package db import ( "fmt" - "io/ioutil" + "io" "net/url" "os" "path/filepath" @@ -189,7 +189,7 @@ func read(path string) ([]byte, error) { } defer f.Close() - d, err := ioutil.ReadAll(f) + d, err := io.ReadAll(f) if err != nil { return nil, err } diff --git a/tm2/pkg/db/go_level_db_test.go b/tm2/pkg/db/go_level_db_test.go index a85bb6e8713..677b8d86c08 100644 --- a/tm2/pkg/db/go_level_db_test.go +++ b/tm2/pkg/db/go_level_db_test.go @@ -9,6 +9,8 @@ import ( ) func TestGoLevelDBNewGoLevelDB(t *testing.T) { + t.Parallel() + dir := t.TempDir() name := fmt.Sprintf("test_%x", randStr(12)) diff --git a/tm2/pkg/db/gorocks_db_test.go b/tm2/pkg/db/gorocks_db_test.go index 9d1b10cef34..d4792424f39 100644 --- a/tm2/pkg/db/gorocks_db_test.go +++ b/tm2/pkg/db/gorocks_db_test.go @@ -10,6 +10,8 @@ import ( ) func TestGoRocksDBBackend(t *testing.T) { + t.Parallel() + name := fmt.Sprintf("test_%x", randStr(12)) db := NewDB(name, GoRocksDBBackend, t.TempDir()) @@ -18,6 +20,8 @@ func TestGoRocksDBBackend(t *testing.T) { } func TestGoRocksDBStats(t *testing.T) { + t.Parallel() + name := fmt.Sprintf("test_%x", randStr(12)) db := NewDB(name, GoRocksDBBackend, t.TempDir()) diff --git a/tm2/pkg/db/grocks_db_test.go b/tm2/pkg/db/grocks_db_test.go index 60592bc8d82..10b33026128 100644 --- a/tm2/pkg/db/grocks_db_test.go +++ b/tm2/pkg/db/grocks_db_test.go @@ -10,6 +10,8 @@ import ( ) func TestGRocksDBBackend(t *testing.T) { + t.Parallel() + name := fmt.Sprintf("test_%x", randStr(12)) db := NewDB(name, GRocksDBBackend, t.TempDir()) @@ -18,6 +20,8 @@ func TestGRocksDBBackend(t *testing.T) { } func TestGRocksDBStats(t *testing.T) { + t.Parallel() + name := fmt.Sprintf("test_%x", randStr(12)) db := NewDB(name, GRocksDBBackend, t.TempDir()) diff --git a/tm2/pkg/db/prefix_db.go b/tm2/pkg/db/prefix_db.go index ced82f922d1..29ed53639e8 100644 --- a/tm2/pkg/db/prefix_db.go +++ b/tm2/pkg/db/prefix_db.go @@ -329,7 +329,7 @@ func stripPrefix(key []byte, prefix []byte) (stripped []byte) { panic("should not happen") } if !bytes.Equal(key[:len(prefix)], prefix) { - panic("should not happne") + panic("should not happen") } return key[len(prefix):] } diff --git a/tm2/pkg/db/prefix_db_test.go b/tm2/pkg/db/prefix_db_test.go index e3e37c7d12c..0948cce98a5 100644 --- a/tm2/pkg/db/prefix_db_test.go +++ b/tm2/pkg/db/prefix_db_test.go @@ -18,6 +18,8 @@ func mockDBWithStuff() DB { } func TestPrefixDBSimple(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -36,6 +38,8 @@ func TestPrefixDBSimple(t *testing.T) { } func TestPrefixDBIterator1(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -54,6 +58,8 @@ func TestPrefixDBIterator1(t *testing.T) { } func TestPrefixDBIterator2(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -64,6 +70,8 @@ func TestPrefixDBIterator2(t *testing.T) { } func TestPrefixDBIterator3(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -82,6 +90,8 @@ func TestPrefixDBIterator3(t *testing.T) { } func TestPrefixDBIterator4(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -92,6 +102,8 @@ func TestPrefixDBIterator4(t *testing.T) { } func TestPrefixDBReverseIterator1(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -110,6 +122,8 @@ func TestPrefixDBReverseIterator1(t *testing.T) { } func TestPrefixDBReverseIterator2(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -128,6 +142,8 @@ func TestPrefixDBReverseIterator2(t *testing.T) { } func TestPrefixDBReverseIterator3(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -138,6 +154,8 @@ func TestPrefixDBReverseIterator3(t *testing.T) { } func TestPrefixDBReverseIterator4(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -148,6 +166,8 @@ func TestPrefixDBReverseIterator4(t *testing.T) { } func TestPrefixDBReverseIterator5(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -164,6 +184,8 @@ func TestPrefixDBReverseIterator5(t *testing.T) { } func TestPrefixDBReverseIterator6(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) @@ -178,6 +200,8 @@ func TestPrefixDBReverseIterator6(t *testing.T) { } func TestPrefixDBReverseIterator7(t *testing.T) { + t.Parallel() + db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) diff --git a/tm2/pkg/db/util_test.go b/tm2/pkg/db/util_test.go index adf52123290..f66fddde64e 100644 --- a/tm2/pkg/db/util_test.go +++ b/tm2/pkg/db/util_test.go @@ -7,8 +7,12 @@ import ( // Empty iterator for empty db. func TestPrefixIteratorNoMatchNil(t *testing.T) { + t.Parallel() + for backend := range backends { t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) itr := IteratePrefix(db, []byte("2")) @@ -19,6 +23,8 @@ func TestPrefixIteratorNoMatchNil(t *testing.T) { // Empty iterator for db populated after iterator created. func TestPrefixIteratorNoMatch1(t *testing.T) { + t.Parallel() + for backend := range backends { if backend == BoltDBBackend { t.Log("bolt does not support concurrent writes while iterating") @@ -26,6 +32,8 @@ func TestPrefixIteratorNoMatch1(t *testing.T) { } t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) itr := IteratePrefix(db, []byte("2")) db.SetSync(bz("1"), bz("value_1")) @@ -37,8 +45,12 @@ func TestPrefixIteratorNoMatch1(t *testing.T) { // Empty iterator for prefix starting after db entry. func TestPrefixIteratorNoMatch2(t *testing.T) { + t.Parallel() + for backend := range backends { t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) db.SetSync(bz("3"), bz("value_3")) itr := IteratePrefix(db, []byte("4")) @@ -50,8 +62,12 @@ func TestPrefixIteratorNoMatch2(t *testing.T) { // Iterator with single val for db with single val, starting from that val. func TestPrefixIteratorMatch1(t *testing.T) { + t.Parallel() + for backend := range backends { t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) db.SetSync(bz("2"), bz("value_2")) itr := IteratePrefix(db, bz("2")) @@ -68,8 +84,12 @@ func TestPrefixIteratorMatch1(t *testing.T) { // Iterator with prefix iterates over everything with same prefix. func TestPrefixIteratorMatches1N(t *testing.T) { + t.Parallel() + for backend := range backends { t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) { + t.Parallel() + db := newTempDB(t, backend) // prefixed diff --git a/tm2/pkg/errors/errors_test.go b/tm2/pkg/errors/errors_test.go index ddd2539738f..21115c21862 100644 --- a/tm2/pkg/errors/errors_test.go +++ b/tm2/pkg/errors/errors_test.go @@ -9,6 +9,8 @@ import ( ) func TestErrorPanic(t *testing.T) { + t.Parallel() + type pnk struct { msg string } @@ -31,6 +33,8 @@ func TestErrorPanic(t *testing.T) { } func TestWrapSomething(t *testing.T) { + t.Parallel() + err := Wrap("something", "formatter%v%v", 0, 1) assert.Equal(t, "something", err.Data()) @@ -40,6 +44,8 @@ func TestWrapSomething(t *testing.T) { } func TestWrapNothing(t *testing.T) { + t.Parallel() + err := Wrap(nil, "formatter%v%v", 0, 1) assert.Equal(t, @@ -51,6 +57,8 @@ func TestWrapNothing(t *testing.T) { } func TestErrorNew(t *testing.T) { + t.Parallel() + err := New("formatter%v%v", 0, 1) assert.Equal(t, @@ -62,6 +70,8 @@ func TestErrorNew(t *testing.T) { } func TestErrorNewWithDetails(t *testing.T) { + t.Parallel() + err := New("formatter%v%v", 0, 1) err.Trace(0, "trace %v", 1) err.Trace(0, "trace %v", 2) @@ -71,6 +81,8 @@ func TestErrorNewWithDetails(t *testing.T) { } func TestErrorNewWithStacktrace(t *testing.T) { + t.Parallel() + err := New("formatter%v%v", 0, 1).Stacktrace() assert.Equal(t, @@ -82,6 +94,8 @@ func TestErrorNewWithStacktrace(t *testing.T) { } func TestErrorNewWithTrace(t *testing.T) { + t.Parallel() + err := New("formatter%v%v", 0, 1) err.Trace(0, "trace %v", 1) err.Trace(0, "trace %v", 2) @@ -100,6 +114,8 @@ func TestErrorNewWithTrace(t *testing.T) { } func TestWrapError(t *testing.T) { + t.Parallel() + var err1 error = New("my message") var err2 error = Wrap(err1, "another message") assert.Equal(t, err1, err2) diff --git a/tm2/pkg/events/events_test.go b/tm2/pkg/events/events_test.go index f3b4aa2fd82..5232364b1c8 100644 --- a/tm2/pkg/events/events_test.go +++ b/tm2/pkg/events/events_test.go @@ -35,6 +35,8 @@ func TestAddListenerFireOnce(t *testing.T) { // TestAddListenerFireMany sets up an EventSwitch, subscribes a single // listener, and sends a thousand integers. func TestAddListenerFireMany(t *testing.T) { + t.Parallel() + evsw := NewEventSwitch() err := evsw.Start() require.NoError(t, err) @@ -62,6 +64,8 @@ func TestAddListenerFireMany(t *testing.T) { // TestAddListeners sets up an EventSwitch, subscribes three // listeners, and sends a thousand integers for each. func TestAddListeners(t *testing.T) { + t.Parallel() + evsw := NewEventSwitch() err := evsw.Start() require.NoError(t, err) @@ -100,6 +104,8 @@ func TestAddListeners(t *testing.T) { } func TestAddAndRemoveListenerConcurrency(t *testing.T) { + t.Parallel() + var ( stopInputEvent = false roundCount = 2000 @@ -145,6 +151,8 @@ func TestAddAndRemoveListenerConcurrency(t *testing.T) { } func TestAddAndRemoveListener(t *testing.T) { + t.Parallel() + evsw := NewEventSwitch() err := evsw.Start() require.NoError(t, err) @@ -192,6 +200,8 @@ func TestAddAndRemoveListener(t *testing.T) { // TestRemoveListener does basic tests on adding and removing func TestRemoveListener(t *testing.T) { + t.Parallel() + evsw := NewEventSwitch() err := evsw.Start() require.NoError(t, err) @@ -241,6 +251,8 @@ func TestRemoveListener(t *testing.T) { // NOTE: it is important to run this test with race conditions tracking on, // `go test -race`, to examine for possible race conditions. func TestRemoveListenersAsync(t *testing.T) { + t.Parallel() + evsw := NewEventSwitch() err := evsw.Start() require.NoError(t, err) diff --git a/tm2/pkg/events/store.go b/tm2/pkg/events/store.go index bea4626394e..1891fd17ac6 100644 --- a/tm2/pkg/events/store.go +++ b/tm2/pkg/events/store.go @@ -1,33 +1,12 @@ package events -import ( - "fmt" - - auto "github.com/gnolang/gno/tm2/pkg/autofile" -) - // StoreStream stores events to disk but is also listenaable. type StoreStream interface { Eventable SetHeight(height int64) // to demarcate height in WAL for replay. } -type storeStream struct { - afile *auto.AutoFile - buf []byte - height int64 -} - -func (ss *storeStream) SetHeight(height int64) { - if ss.height < height { - // write new height - ss.height = height - } else /* if height <= ss.height */ { - panic(fmt.Sprintf("invalid SetHeight height value. current %v, got %v", ss.height, height)) - } -} - -//---------------------------------------- +// ---------------------------------------- // move to own file // FilterStream is listenable and lets you filter. diff --git a/tm2/pkg/flow/io_test.go b/tm2/pkg/flow/io_test.go index 196c7c65d9a..d023d32c347 100644 --- a/tm2/pkg/flow/io_test.go +++ b/tm2/pkg/flow/io_test.go @@ -32,6 +32,8 @@ func nextStatus(m *Monitor) Status { } func TestReader(t *testing.T) { + t.Parallel() + in := make([]byte, 100) for i := range in { in[i] = byte(i) @@ -101,6 +103,8 @@ func TestReader(t *testing.T) { } func TestWriter(t *testing.T) { + t.Parallel() + b := make([]byte, 100) for i := range b { b[i] = byte(i) diff --git a/tm2/pkg/iavl/basic_test.go b/tm2/pkg/iavl/basic_test.go index ab46792aee4..f684209a682 100644 --- a/tm2/pkg/iavl/basic_test.go +++ b/tm2/pkg/iavl/basic_test.go @@ -13,6 +13,8 @@ import ( ) func TestBasic(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 0) up := tree.Set([]byte("1"), []byte("one")) if up { @@ -103,6 +105,8 @@ func TestBasic(t *testing.T) { } func TestUnit(t *testing.T) { + t.Parallel() + expectHash := func(tree *ImmutableTree, hashCount int64) { // ensure number of new hash calculations is as expected. hash, count := tree.hashWithCount() @@ -184,6 +188,8 @@ func TestUnit(t *testing.T) { } func TestRemove(t *testing.T) { + t.Parallel() + size := 10000 keyLen, dataLen := 16, 40 @@ -214,6 +220,8 @@ func TestRemove(t *testing.T) { } func TestIntegration(t *testing.T) { + t.Parallel() + type record struct { key string value string @@ -279,6 +287,8 @@ func TestIntegration(t *testing.T) { } func TestIterateRange(t *testing.T) { + t.Parallel() + type record struct { key string value string @@ -363,6 +373,8 @@ func TestIterateRange(t *testing.T) { } func TestPersistence(t *testing.T) { + t.Parallel() + db := db.NewMemDB() // Create some random key value pairs @@ -390,6 +402,8 @@ func TestPersistence(t *testing.T) { } func TestProof(t *testing.T) { + t.Parallel() + // Construct some random tree db := db.NewMemDB() tree := NewMutableTree(db, 100) @@ -420,6 +434,8 @@ func TestProof(t *testing.T) { } func TestTreeProof(t *testing.T) { + t.Parallel() + db := db.NewMemDB() tree := NewMutableTree(db, 100) assert.Equal(t, tree.Hash(), []byte(nil)) diff --git a/tm2/pkg/iavl/benchmarks/bench_test.go b/tm2/pkg/iavl/benchmarks/bench_test.go index de6a573020e..77ad77eed28 100644 --- a/tm2/pkg/iavl/benchmarks/bench_test.go +++ b/tm2/pkg/iavl/benchmarks/bench_test.go @@ -75,19 +75,6 @@ func runKnownQueries(b *testing.B, t *iavl.MutableTree, keys [][]byte) { } } -func runInsert(b *testing.B, t *iavl.MutableTree, keyLen, dataLen, blockSize int) *iavl.MutableTree { - b.Helper() - - for i := 1; i <= b.N; i++ { - t.Set(randBytes(keyLen), randBytes(dataLen)) - if i%blockSize == 0 { - t.Hash() - t.SaveVersion() - } - } - return t -} - func runUpdate(b *testing.B, t *iavl.MutableTree, dataLen, blockSize int, keys [][]byte) *iavl.MutableTree { b.Helper() @@ -102,23 +89,6 @@ func runUpdate(b *testing.B, t *iavl.MutableTree, dataLen, blockSize int, keys [ return t } -func runDelete(b *testing.B, t *iavl.MutableTree, blockSize int, keys [][]byte) *iavl.MutableTree { - b.Helper() - - var key []byte - l := int32(len(keys)) - for i := 1; i <= b.N; i++ { - key = keys[rand.Int31n(l)] - // key = randBytes(16) - // TODO: test if removed, use more keys (from insert) - t.Remove(key) - if i%blockSize == 0 { - commitTree(b, t) - } - } - return t -} - // runBlock measures time for an entire block, not just one tx func runBlock(b *testing.B, t *iavl.MutableTree, keyLen, dataLen, blockSize int, keys [][]byte) *iavl.MutableTree { b.Helper() diff --git a/tm2/pkg/iavl/common/random_test.go b/tm2/pkg/iavl/common/random_test.go index 6ed73ab898c..4c1ee8024e0 100644 --- a/tm2/pkg/iavl/common/random_test.go +++ b/tm2/pkg/iavl/common/random_test.go @@ -11,12 +11,16 @@ import ( ) func TestRandStr(t *testing.T) { + t.Parallel() + l := 243 s := RandStr(l) assert.Equal(t, l, len(s)) } func TestRandBytes(t *testing.T) { + t.Parallel() + l := 243 b := RandBytes(l) assert.Equal(t, l, len(b)) diff --git a/tm2/pkg/iavl/node.go b/tm2/pkg/iavl/node.go index f2a944afc6a..ee522016f0c 100644 --- a/tm2/pkg/iavl/node.go +++ b/tm2/pkg/iavl/node.go @@ -371,10 +371,6 @@ func (node *Node) traverse(t *ImmutableTree, ascending bool, cb func(*Node) bool }) } -func (node *Node) traverseWithDepth(t *ImmutableTree, ascending bool, cb func(*Node, uint8) bool) bool { - return node.traverseInRange(t, nil, nil, ascending, false, 0, cb) -} - func (node *Node) traverseInRange(t *ImmutableTree, start, end []byte, ascending bool, inclusive bool, depth uint8, cb func(*Node, uint8) bool) bool { afterStart := start == nil || bytes.Compare(start, node.key) < 0 startOrAfter := start == nil || bytes.Compare(start, node.key) <= 0 diff --git a/tm2/pkg/iavl/nodedb.go b/tm2/pkg/iavl/nodedb.go index 3e59b3480e9..e1998d8cc1d 100644 --- a/tm2/pkg/iavl/nodedb.go +++ b/tm2/pkg/iavl/nodedb.go @@ -385,7 +385,7 @@ func (ndb *nodeDB) saveRoot(hash []byte, version int64) error { return nil } -////////////////// Utility and test functions ///////////////////////////////// +// ----------- Utility and test functions // ----------- func (ndb *nodeDB) leafNodes() []*Node { leaves := []*Node{} diff --git a/tm2/pkg/iavl/proof_forgery_test.go b/tm2/pkg/iavl/proof_forgery_test.go index a742ad40fcc..028675f7c48 100644 --- a/tm2/pkg/iavl/proof_forgery_test.go +++ b/tm2/pkg/iavl/proof_forgery_test.go @@ -14,6 +14,8 @@ import ( ) func TestProofForgery(t *testing.T) { + t.Parallel() + source := rand.NewSource(0) r := rand.New(source) cacheSize := 0 diff --git a/tm2/pkg/iavl/proof_path.go b/tm2/pkg/iavl/proof_path.go index f66fcf3c48c..a5ac14bc4e7 100644 --- a/tm2/pkg/iavl/proof_path.go +++ b/tm2/pkg/iavl/proof_path.go @@ -1,11 +1,8 @@ package iavl import ( - "bytes" "fmt" "strings" - - "github.com/gnolang/gno/tm2/pkg/errors" ) // pathWithLeaf is a path to a leaf node and the leaf node itself. @@ -28,14 +25,6 @@ func (pwl pathWithLeaf) StringIndented(indent string) string { indent) } -// `verify` checks that the leaf node's hash + the inner nodes merkle-izes to -// the given root. If it returns an error, it means the leafHash or the -// PathToLeaf is incorrect. -func (pwl pathWithLeaf) verify(root []byte) error { - leafHash := pwl.Leaf.Hash() - return pwl.Path.verify(leafHash, root) -} - // `computeRootHash` computes the root hash with leaf node. // Does not verify the root hash. func (pwl pathWithLeaf) computeRootHash() []byte { @@ -73,21 +62,6 @@ func (pl PathToLeaf) stringIndented(indent string) string { indent) } -// `verify` checks that the leaf node's hash + the inner nodes merkle-izes to -// the given root. If it returns an error, it means the leafHash or the -// PathToLeaf is incorrect. -func (pl PathToLeaf) verify(leafHash []byte, root []byte) error { - hash := leafHash - for i := len(pl) - 1; i >= 0; i-- { - pin := pl[i] - hash = pin.Hash(hash) - } - if !bytes.Equal(root, hash) { - return errors.Wrap(ErrInvalidProof, "") - } - return nil -} - // `computeRootHash` computes the root hash assuming some leaf hash. // Does not verify the root hash. func (pl PathToLeaf) computeRootHash(leafHash []byte) []byte { @@ -117,37 +91,6 @@ func (pl PathToLeaf) isRightmost() bool { return true } -func (pl PathToLeaf) isEmpty() bool { - return pl == nil || len(pl) == 0 -} - -func (pl PathToLeaf) dropRoot() PathToLeaf { - if pl.isEmpty() { - return pl - } - return pl[:len(pl)-1] -} - -func (pl PathToLeaf) hasCommonRoot(pl2 PathToLeaf) bool { - if pl.isEmpty() || pl2.isEmpty() { - return false - } - leftEnd := pl[len(pl)-1] - rightEnd := pl2[len(pl2)-1] - - return bytes.Equal(leftEnd.Left, rightEnd.Left) && - bytes.Equal(leftEnd.Right, rightEnd.Right) -} - -func (pl PathToLeaf) isLeftAdjacentTo(pl2 PathToLeaf) bool { - for pl.hasCommonRoot(pl2) { - pl, pl2 = pl.dropRoot(), pl2.dropRoot() - } - pl, pl2 = pl.dropRoot(), pl2.dropRoot() - - return pl.isRightmost() && pl2.isLeftmost() -} - // returns -1 if invalid. func (pl PathToLeaf) Index() (idx int64) { for i, node := range pl { diff --git a/tm2/pkg/iavl/proof_range.go b/tm2/pkg/iavl/proof_range.go index 799fb65cebb..ea6bce24fc0 100644 --- a/tm2/pkg/iavl/proof_range.go +++ b/tm2/pkg/iavl/proof_range.go @@ -299,7 +299,7 @@ func (proof *RangeProof) _computeRootHash() (rootHash []byte, treeEnd bool, err return rootHash, treeEnd, nil } -/////////////////////////////////////////////////////////////////////////////// +// ----------- // keyStart is inclusive and keyEnd is exclusive. // If keyStart or keyEnd don't exist, the leaf before keyStart @@ -442,7 +442,7 @@ func (t *ImmutableTree) getRangeProof(keyStart, keyEnd []byte, limit int) (proof }, keys, values, nil } -//---------------------------------------- +// ---------------------------------------- // GetWithProof gets the value under the key if it exists, or returns nil. // A proof of existence or absence is returned alongside the value. diff --git a/tm2/pkg/iavl/proof_test.go b/tm2/pkg/iavl/proof_test.go index f67ea45b00a..ad4d85d1cd5 100644 --- a/tm2/pkg/iavl/proof_test.go +++ b/tm2/pkg/iavl/proof_test.go @@ -15,6 +15,8 @@ import ( ) func TestTreeGetWithProof(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 0) require := require.New(t) for _, ikey := range []byte{0x11, 0x32, 0x50, 0x72, 0x99} { @@ -49,6 +51,8 @@ func TestTreeGetWithProof(t *testing.T) { } func TestTreeKeyExistsProof(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 0) root := tree.WorkingHash() @@ -115,6 +119,8 @@ func TestTreeKeyExistsProof(t *testing.T) { } func TestTreeKeyInRangeProofs(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 0) require := require.New(t) keys := []byte{0x0a, 0x11, 0x2e, 0x32, 0x50, 0x72, 0x99, 0xa1, 0xe4, 0xf7} // 10 total. diff --git a/tm2/pkg/iavl/tree_dotgraph_test.go b/tm2/pkg/iavl/tree_dotgraph_test.go index 29be03ca241..3c0233f51fa 100644 --- a/tm2/pkg/iavl/tree_dotgraph_test.go +++ b/tm2/pkg/iavl/tree_dotgraph_test.go @@ -1,13 +1,15 @@ package iavl import ( - "io/ioutil" + "io" "testing" db "github.com/gnolang/gno/tm2/pkg/db" ) func TestWriteDOTGraph(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 0) for _, ikey := range []byte{ 0x0a, 0x11, 0x2e, 0x32, 0x50, 0x72, 0x99, 0xa1, 0xe4, 0xf7, @@ -15,5 +17,5 @@ func TestWriteDOTGraph(t *testing.T) { key := []byte{ikey} tree.Set(key, key) } - WriteDOTGraph(ioutil.Discard, tree.ImmutableTree, []PathToLeaf{}) + WriteDOTGraph(io.Discard, tree.ImmutableTree, []PathToLeaf{}) } diff --git a/tm2/pkg/iavl/tree_fuzz_test.go b/tm2/pkg/iavl/tree_fuzz_test.go index 7ea9bb50389..64e20d944a2 100644 --- a/tm2/pkg/iavl/tree_fuzz_test.go +++ b/tm2/pkg/iavl/tree_fuzz_test.go @@ -105,6 +105,8 @@ func genRandomProgram(size int) *program { // Generate many programs and run them. func TestMutableTreeFuzz(t *testing.T) { + t.Parallel() + maxIterations := testFuzzIterations progsPerIteration := 100000 iterations := 0 diff --git a/tm2/pkg/iavl/tree_test.go b/tm2/pkg/iavl/tree_test.go index 56a00ee2424..ba5bbe3e5ef 100644 --- a/tm2/pkg/iavl/tree_test.go +++ b/tm2/pkg/iavl/tree_test.go @@ -51,6 +51,8 @@ func getTestDB() (db.DB, func()) { } func TestVersionedRandomTree(t *testing.T) { + t.Parallel() + require := require.New(t) d, closeDB := getTestDB() @@ -105,6 +107,8 @@ func TestVersionedRandomTree(t *testing.T) { } func TestVersionedRandomTreeSmallKeys(t *testing.T) { + t.Parallel() + require := require.New(t) d, closeDB := getTestDB() defer closeDB() @@ -146,6 +150,8 @@ func TestVersionedRandomTreeSmallKeys(t *testing.T) { } func TestVersionedRandomTreeSmallKeysRandomDeletes(t *testing.T) { + t.Parallel() + require := require.New(t) d, closeDB := getTestDB() defer closeDB() @@ -187,6 +193,8 @@ func TestVersionedRandomTreeSmallKeysRandomDeletes(t *testing.T) { } func TestVersionedTreeSpecial1(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 100) tree.Set([]byte("C"), []byte("so43QQFN")) @@ -209,6 +217,8 @@ func TestVersionedTreeSpecial1(t *testing.T) { } func TestVersionedRandomTreeSpecial2(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 100) @@ -225,6 +235,8 @@ func TestVersionedRandomTreeSpecial2(t *testing.T) { } func TestVersionedEmptyTree(t *testing.T) { + t.Parallel() + require := require.New(t) d, closeDB := getTestDB() defer closeDB() @@ -281,6 +293,8 @@ func TestVersionedEmptyTree(t *testing.T) { } func TestVersionedTree(t *testing.T) { + t.Parallel() + require := require.New(t) d, closeDB := getTestDB() defer closeDB() @@ -468,6 +482,8 @@ func TestVersionedTree(t *testing.T) { } func TestVersionedTreeVersionDeletingEfficiency(t *testing.T) { + t.Parallel() + d, closeDB := getTestDB() defer closeDB() @@ -512,6 +528,8 @@ func TestVersionedTreeVersionDeletingEfficiency(t *testing.T) { } func TestVersionedTreeOrphanDeleting(t *testing.T) { + t.Parallel() + mdb := db.NewMemDB() tree := NewMutableTree(mdb, 0) @@ -550,6 +568,8 @@ func TestVersionedTreeOrphanDeleting(t *testing.T) { } func TestVersionedTreeSpecialCase(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 100) @@ -571,6 +591,8 @@ func TestVersionedTreeSpecialCase(t *testing.T) { } func TestVersionedTreeSpecialCase2(t *testing.T) { + t.Parallel() + require := require.New(t) d := db.NewMemDB() @@ -598,6 +620,8 @@ func TestVersionedTreeSpecialCase2(t *testing.T) { } func TestVersionedTreeSpecialCase3(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 0) @@ -626,6 +650,8 @@ func TestVersionedTreeSpecialCase3(t *testing.T) { } func TestVersionedTreeSaveAndLoad(t *testing.T) { + t.Parallel() + require := require.New(t) d := db.NewMemDB() tree := NewMutableTree(d, 0) @@ -677,6 +703,8 @@ func TestVersionedTreeSaveAndLoad(t *testing.T) { } func TestVersionedTreeErrors(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 100) @@ -706,6 +734,8 @@ func TestVersionedTreeErrors(t *testing.T) { } func TestVersionedCheckpoints(t *testing.T) { + t.Parallel() + require := require.New(t) d, closeDB := getTestDB() defer closeDB() @@ -762,6 +792,8 @@ func TestVersionedCheckpoints(t *testing.T) { } func TestVersionedCheckpointsSpecialCase(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 0) key := []byte("k") @@ -788,6 +820,8 @@ func TestVersionedCheckpointsSpecialCase(t *testing.T) { } func TestVersionedCheckpointsSpecialCase2(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 0) tree.Set([]byte("U"), []byte("XamDUtiJ")) @@ -808,6 +842,8 @@ func TestVersionedCheckpointsSpecialCase2(t *testing.T) { } func TestVersionedCheckpointsSpecialCase3(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 0) tree.Set([]byte("n"), []byte("2wUCUs8q")) @@ -828,6 +864,8 @@ func TestVersionedCheckpointsSpecialCase3(t *testing.T) { } func TestVersionedCheckpointsSpecialCase4(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 0) tree.Set([]byte("U"), []byte("XamDUtiJ")) @@ -860,6 +898,8 @@ func TestVersionedCheckpointsSpecialCase4(t *testing.T) { } func TestVersionedCheckpointsSpecialCase5(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 0) tree.Set([]byte("R"), []byte("ygZlIzeW")) @@ -877,6 +917,8 @@ func TestVersionedCheckpointsSpecialCase5(t *testing.T) { } func TestVersionedCheckpointsSpecialCase6(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 0) tree.Set([]byte("Y"), []byte("MW79JQeV")) @@ -909,6 +951,8 @@ func TestVersionedCheckpointsSpecialCase6(t *testing.T) { } func TestVersionedCheckpointsSpecialCase7(t *testing.T) { + t.Parallel() + tree := NewMutableTree(db.NewMemDB(), 100) tree.Set([]byte("n"), []byte("OtqD3nyn")) @@ -942,6 +986,8 @@ func TestVersionedCheckpointsSpecialCase7(t *testing.T) { } func TestVersionedTreeEfficiency(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 0) versions := 20 @@ -977,6 +1023,8 @@ func TestVersionedTreeEfficiency(t *testing.T) { } func TestVersionedTreeProofs(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 0) @@ -1047,6 +1095,8 @@ func TestVersionedTreeProofs(t *testing.T) { } func TestOrphans(t *testing.T) { + t.Parallel() + // If you create a sequence of saved versions // Then randomly delete versions other than the first and last until only those two remain // Any remaining orphan nodes should be constrained to just the first version @@ -1079,6 +1129,8 @@ func TestOrphans(t *testing.T) { } func TestVersionedTreeHash(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 0) @@ -1101,6 +1153,8 @@ func TestVersionedTreeHash(t *testing.T) { } func TestNilValueSemantics(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 0) @@ -1110,6 +1164,8 @@ func TestNilValueSemantics(t *testing.T) { } func TestCopyValueSemantics(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 0) @@ -1127,6 +1183,8 @@ func TestCopyValueSemantics(t *testing.T) { } func TestRollback(t *testing.T) { + t.Parallel() + require := require.New(t) tree := NewMutableTree(db.NewMemDB(), 0) @@ -1156,6 +1214,8 @@ func TestRollback(t *testing.T) { } func TestLazyLoadVersion(t *testing.T) { + t.Parallel() + mdb := db.NewMemDB() tree := NewMutableTree(mdb, 0) maxVersions := 10 @@ -1194,6 +1254,8 @@ func TestLazyLoadVersion(t *testing.T) { } func TestOverwrite(t *testing.T) { + t.Parallel() + require := require.New(t) mdb := db.NewMemDB() @@ -1226,6 +1288,8 @@ func TestOverwrite(t *testing.T) { } func TestLoadVersionForOverwriting(t *testing.T) { + t.Parallel() + require := require.New(t) mdb := db.NewMemDB() @@ -1290,7 +1354,7 @@ func TestLoadVersionForOverwriting(t *testing.T) { require.NoError(err, "SaveVersion should not fail.") } -//////////////////////////// BENCHMARKS /////////////////////////////////////// +// ----------- BENCHMARKS // ----------- func BenchmarkTreeLoadAndDelete(b *testing.B) { if testing.Short() { diff --git a/tm2/pkg/log/tm_logger_test.go b/tm2/pkg/log/tm_logger_test.go index a6d56bb8feb..43ca98eb0f9 100644 --- a/tm2/pkg/log/tm_logger_test.go +++ b/tm2/pkg/log/tm_logger_test.go @@ -2,7 +2,7 @@ package log_test import ( "bytes" - "io/ioutil" + "io" "strings" "testing" @@ -10,6 +10,8 @@ import ( ) func TestLoggerLogsItsErrors(t *testing.T) { + t.Parallel() + var buf bytes.Buffer logger := log.NewTMLogger(&buf) @@ -21,11 +23,11 @@ func TestLoggerLogsItsErrors(t *testing.T) { } func BenchmarkTMLoggerSimple(b *testing.B) { - benchmarkRunner(b, log.NewTMLogger(ioutil.Discard), baseInfoMessage) + benchmarkRunner(b, log.NewTMLogger(io.Discard), baseInfoMessage) } func BenchmarkTMLoggerContextual(b *testing.B) { - benchmarkRunner(b, log.NewTMLogger(ioutil.Discard), withInfoMessage) + benchmarkRunner(b, log.NewTMLogger(io.Discard), withInfoMessage) } func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) { diff --git a/tm2/pkg/os/net_test.go b/tm2/pkg/os/net_test.go index 258ee28bb28..8cbba60aef2 100644 --- a/tm2/pkg/os/net_test.go +++ b/tm2/pkg/os/net_test.go @@ -7,6 +7,8 @@ import ( ) func TestProtocolAndAddress(t *testing.T) { + t.Parallel() + cases := []struct { fullAddr string proto string diff --git a/tm2/pkg/os/tempfile_test.go b/tm2/pkg/os/tempfile_test.go index 12d6abb27cb..ec294d58e30 100644 --- a/tm2/pkg/os/tempfile_test.go +++ b/tm2/pkg/os/tempfile_test.go @@ -5,7 +5,6 @@ package os import ( "bytes" "fmt" - "io/ioutil" "os" "testing" @@ -21,7 +20,7 @@ func TestWriteFileAtomic(t *testing.T) { perm os.FileMode = 0o600 ) - f, err := ioutil.TempFile("/tmp", "write-atomic-test-") + f, err := os.CreateTemp("/tmp", "write-atomic-test-") if err != nil { t.Fatal(err) } diff --git a/tm2/pkg/p2p/config/config.go b/tm2/pkg/p2p/config/config.go index 855c0fd9844..830f84ad137 100644 --- a/tm2/pkg/p2p/config/config.go +++ b/tm2/pkg/p2p/config/config.go @@ -6,7 +6,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/errors" ) -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // P2PConfig const ( @@ -16,8 +16,6 @@ const ( FuzzModeDelay ) -var defaultConfigDir = "config" // duplicate across module configs? - // P2PConfig defines the configuration options for the Tendermint peer-to-peer networking layer type P2PConfig struct { RootDir string `toml:"home"` diff --git a/tm2/pkg/p2p/conn/connection_test.go b/tm2/pkg/p2p/conn/connection_test.go index 6974dd97e56..68f7fbc0841 100644 --- a/tm2/pkg/p2p/conn/connection_test.go +++ b/tm2/pkg/p2p/conn/connection_test.go @@ -37,6 +37,8 @@ func createMConnectionWithCallbacks(conn net.Conn, onReceive func(chID byte, msg } func TestMConnectionSendFlushStop(t *testing.T) { + t.Parallel() + server, client := NetPipe() defer server.Close() //nolint: errcheck defer client.Close() //nolint: errcheck @@ -82,6 +84,8 @@ func TestMConnectionSendFlushStop(t *testing.T) { } func TestMConnectionSend(t *testing.T) { + t.Parallel() + server, client := NetPipe() defer server.Close() //nolint: errcheck defer client.Close() //nolint: errcheck @@ -113,6 +117,8 @@ func TestMConnectionSend(t *testing.T) { } func TestMConnectionReceive(t *testing.T) { + t.Parallel() + server, client := NetPipe() defer server.Close() //nolint: errcheck defer client.Close() //nolint: errcheck @@ -149,6 +155,8 @@ func TestMConnectionReceive(t *testing.T) { } func TestMConnectionStatus(t *testing.T) { + t.Parallel() + server, client := NetPipe() defer server.Close() //nolint: errcheck defer client.Close() //nolint: errcheck @@ -164,6 +172,8 @@ func TestMConnectionStatus(t *testing.T) { } func TestMConnectionPongTimeoutResultsInError(t *testing.T) { + t.Parallel() + server, client := net.Pipe() defer server.Close() defer client.Close() @@ -203,6 +213,8 @@ func TestMConnectionPongTimeoutResultsInError(t *testing.T) { } func TestMConnectionMultiplePongsInTheBeginning(t *testing.T) { + t.Parallel() + server, client := net.Pipe() defer server.Close() defer client.Close() @@ -256,6 +268,8 @@ func TestMConnectionMultiplePongsInTheBeginning(t *testing.T) { } func TestMConnectionMultiplePings(t *testing.T) { + t.Parallel() + server, client := net.Pipe() defer server.Close() defer client.Close() @@ -293,6 +307,8 @@ func TestMConnectionMultiplePings(t *testing.T) { } func TestMConnectionPingPongs(t *testing.T) { + t.Parallel() + // check that we are not leaking any go-routines defer leaktest.CheckTimeout(t, 10*time.Second)() @@ -348,6 +364,8 @@ func TestMConnectionPingPongs(t *testing.T) { } func TestMConnectionStopsAndReturnsError(t *testing.T) { + t.Parallel() + server, client := NetPipe() defer server.Close() //nolint: errcheck defer client.Close() //nolint: errcheck @@ -422,6 +440,8 @@ func expectSend(ch chan struct{}) bool { } func TestMConnectionReadErrorBadEncoding(t *testing.T) { + t.Parallel() + chOnErr := make(chan struct{}) mconnClient, mconnServer := newClientAndServerConnsForReadErrors(t, chOnErr) defer mconnClient.Stop() @@ -440,6 +460,8 @@ func TestMConnectionReadErrorBadEncoding(t *testing.T) { } func TestMConnectionReadErrorUnknownChannel(t *testing.T) { + t.Parallel() + chOnErr := make(chan struct{}) mconnClient, mconnServer := newClientAndServerConnsForReadErrors(t, chOnErr) defer mconnClient.Stop() @@ -457,6 +479,8 @@ func TestMConnectionReadErrorUnknownChannel(t *testing.T) { } func TestMConnectionReadErrorLongMessage(t *testing.T) { + t.Parallel() + chOnErr := make(chan struct{}) chOnRcv := make(chan struct{}) @@ -499,6 +523,8 @@ func TestMConnectionReadErrorLongMessage(t *testing.T) { } func TestMConnectionReadErrorUnknownMsgType(t *testing.T) { + t.Parallel() + chOnErr := make(chan struct{}) mconnClient, mconnServer := newClientAndServerConnsForReadErrors(t, chOnErr) defer mconnClient.Stop() @@ -513,6 +539,8 @@ func TestMConnectionReadErrorUnknownMsgType(t *testing.T) { } func TestMConnectionTrySend(t *testing.T) { + t.Parallel() + server, client := NetPipe() defer server.Close() defer client.Close() diff --git a/tm2/pkg/p2p/conn/secret_connection_test.go b/tm2/pkg/p2p/conn/secret_connection_test.go index 521f651e78b..b238297ae5e 100644 --- a/tm2/pkg/p2p/conn/secret_connection_test.go +++ b/tm2/pkg/p2p/conn/secret_connection_test.go @@ -98,6 +98,8 @@ func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection } func TestSecretConnectionHandshake(t *testing.T) { + t.Parallel() + fooSecConn, barSecConn := makeSecretConnPair(t) if err := fooSecConn.Close(); err != nil { t.Error(err) @@ -110,6 +112,8 @@ func TestSecretConnectionHandshake(t *testing.T) { // Test that shareEphPubKey rejects lower order public keys based on an // (incomplete) blacklist. func TestShareLowOrderPubkey(t *testing.T) { + t.Parallel() + fooConn, barConn := makeKVStoreConnPair() defer fooConn.Close() defer barConn.Close() @@ -141,6 +145,8 @@ func TestShareLowOrderPubkey(t *testing.T) { // Test that additionally that the Diffie-Hellman shared secret is non-zero. // The shared secret would be zero for lower order pub-keys (but tested against the blacklist only). func TestComputeDHFailsOnLowOrder(t *testing.T) { + t.Parallel() + _, locPrivKey := genEphKeys() for _, remLowOrderPubKey := range blacklist { remLowOrderPubKey := remLowOrderPubKey @@ -153,6 +159,8 @@ func TestComputeDHFailsOnLowOrder(t *testing.T) { } func TestConcurrentWrite(t *testing.T) { + t.Parallel() + fooSecConn, barSecConn := makeSecretConnPair(t) fooWriteText := random.RandStr(dataMaxSize) @@ -175,6 +183,8 @@ func TestConcurrentWrite(t *testing.T) { } func TestConcurrentRead(t *testing.T) { + t.Parallel() + fooSecConn, barSecConn := makeSecretConnPair(t) fooWriteText := random.RandStr(dataMaxSize) n := 100 @@ -221,6 +231,8 @@ func readLots(t *testing.T, wg *sync.WaitGroup, conn net.Conn, n int) { } func TestSecretConnectionReadWrite(t *testing.T) { + t.Parallel() + fooConn, barConn := makeKVStoreConnPair() fooWrites, barWrites := []string{}, []string{} fooReads, barReads := []string{}, []string{} @@ -234,7 +246,7 @@ func TestSecretConnectionReadWrite(t *testing.T) { // A helper that will run with (fooConn, fooWrites, fooReads) and vice versa genNodeRunner := func(id string, nodeConn kvstoreConn, nodeWrites []string, nodeReads *[]string) async.Task { return func(_ int) (interface{}, error, bool) { - // Initiate cryptographic private key and secret connection trhough nodeConn. + // Initiate cryptographic private key and secret connection through nodeConn. nodePrvKey := ed25519.GenPrivKey() nodeSecretConn, err := MakeSecretConnection(nodeConn, nodePrvKey) if err != nil { @@ -341,6 +353,8 @@ func TestSecretConnectionReadWrite(t *testing.T) { var update = flag.Bool("update", false, "update .golden files") func TestDeriveSecretsAndChallengeGolden(t *testing.T) { + t.Parallel() + goldenFilepath := filepath.Join("testdata", t.Name()+".golden") if *update { t.Logf("Updating golden test vector file %s", goldenFilepath) @@ -386,6 +400,8 @@ func (pk privKeyWithNilPubKey) PubKey() crypto.PubKey { return nil } func (pk privKeyWithNilPubKey) Equals(pk2 crypto.PrivKey) bool { return pk.orig.Equals(pk2) } func TestNilPubkey(t *testing.T) { + t.Parallel() + fooConn, barConn := makeKVStoreConnPair() fooPrvKey := ed25519.GenPrivKey() barPrvKey := privKeyWithNilPubKey{ed25519.GenPrivKey()} @@ -404,6 +420,8 @@ func TestNilPubkey(t *testing.T) { } func TestNonEd25519Pubkey(t *testing.T) { + t.Parallel() + fooConn, barConn := makeKVStoreConnPair() fooPrvKey := ed25519.GenPrivKey() barPrvKey := secp256k1.GenPrivKey() diff --git a/tm2/pkg/p2p/key_test.go b/tm2/pkg/p2p/key_test.go index 8046f58eb25..4f67cc0a5da 100644 --- a/tm2/pkg/p2p/key_test.go +++ b/tm2/pkg/p2p/key_test.go @@ -11,6 +11,8 @@ import ( ) func TestLoadOrGenNodeKey(t *testing.T) { + t.Parallel() + filePath := filepath.Join(os.TempDir(), random.RandStr(12)+"_peer_id.json") nodeKey, err := LoadOrGenNodeKey(filePath) @@ -22,13 +24,15 @@ func TestLoadOrGenNodeKey(t *testing.T) { assert.Equal(t, nodeKey, nodeKey2) } -//---------------------------------------------------------- +// ---------------------------------------------------------- func padBytes(bz []byte, targetBytes int) []byte { return append(bz, bytes.Repeat([]byte{0xFF}, targetBytes-len(bz))...) } func TestPoWTarget(t *testing.T) { + t.Parallel() + targetBytes := 20 cases := []struct { difficulty uint diff --git a/tm2/pkg/p2p/netaddress_test.go b/tm2/pkg/p2p/netaddress_test.go index a5dc3fca375..413d020c153 100644 --- a/tm2/pkg/p2p/netaddress_test.go +++ b/tm2/pkg/p2p/netaddress_test.go @@ -11,6 +11,8 @@ import ( ) func TestAddress2ID(t *testing.T) { + t.Parallel() + idbz, _ := hex.DecodeString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") id := crypto.AddressFromBytes(idbz).ID() assert.Equal(t, crypto.ID("g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6"), id) @@ -21,6 +23,8 @@ func TestAddress2ID(t *testing.T) { } func TestNewNetAddress(t *testing.T) { + t.Parallel() + tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080") require.Nil(t, err) @@ -41,6 +45,8 @@ func TestNewNetAddress(t *testing.T) { } func TestNewNetAddressFromString(t *testing.T) { + t.Parallel() + testCases := []struct { name string addr string @@ -85,6 +91,8 @@ func TestNewNetAddressFromString(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() + addr, err := NewNetAddressFromString(tc.addr) if tc.correct { if assert.Nil(t, err, tc.addr) { @@ -98,6 +106,8 @@ func TestNewNetAddressFromString(t *testing.T) { } func TestNewNetAddressFromStrings(t *testing.T) { + t.Parallel() + addrs, errs := NewNetAddressFromStrings([]string{ "127.0.0.1:8080", "g1m6kmam774klwlh4dhmhaatd7al02m0h0jwnyc6@127.0.0.1:8080", @@ -108,11 +118,15 @@ func TestNewNetAddressFromStrings(t *testing.T) { } func TestNewNetAddressFromIPPort(t *testing.T) { + t.Parallel() + addr := NewNetAddressFromIPPort("", net.ParseIP("127.0.0.1"), 8080) assert.Equal(t, "127.0.0.1:8080", addr.String()) } func TestNetAddressProperties(t *testing.T) { + t.Parallel() + // TODO add more test cases testCases := []struct { addr string @@ -140,6 +154,8 @@ func TestNetAddressProperties(t *testing.T) { } func TestNetAddressReachabilityTo(t *testing.T) { + t.Parallel() + // TODO add more test cases testCases := []struct { addr string diff --git a/tm2/pkg/p2p/node_info.go b/tm2/pkg/p2p/node_info.go index 9653a83c38a..48ba8f7776b 100644 --- a/tm2/pkg/p2p/node_info.go +++ b/tm2/pkg/p2p/node_info.go @@ -3,6 +3,7 @@ package p2p import ( "fmt" + "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore" "github.com/gnolang/gno/tm2/pkg/strings" "github.com/gnolang/gno/tm2/pkg/versionset" ) @@ -100,7 +101,7 @@ func (info NodeInfo) Validate() error { other := info.Other txIndex := other.TxIndex switch txIndex { - case "", "on", "off": + case "", eventstore.StatusOn, eventstore.StatusOff: default: return fmt.Errorf("info.Other.TxIndex should be either 'on', 'off', or empty string, got '%v'", txIndex) } diff --git a/tm2/pkg/p2p/node_info_test.go b/tm2/pkg/p2p/node_info_test.go index ea97239643f..58f1dab8854 100644 --- a/tm2/pkg/p2p/node_info_test.go +++ b/tm2/pkg/p2p/node_info_test.go @@ -11,6 +11,8 @@ import ( ) func TestNodeInfoValidate(t *testing.T) { + t.Parallel() + // empty fails ni := NodeInfo{} assert.Error(t, ni.Validate()) @@ -86,6 +88,8 @@ func TestNodeInfoValidate(t *testing.T) { } func TestNodeInfoCompatible(t *testing.T) { + t.Parallel() + nodeKey1 := NodeKey{PrivKey: ed25519.GenPrivKey()} nodeKey2 := NodeKey{PrivKey: ed25519.GenPrivKey()} name := "testing" diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index 070d783d2af..8841e34cc83 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -19,6 +19,8 @@ import ( ) func TestPeerBasic(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) // simulate remote peer @@ -43,6 +45,8 @@ func TestPeerBasic(t *testing.T) { } func TestPeerSend(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) config := cfg diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 85ded2cb880..6fe1ccde4a1 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -393,10 +393,6 @@ func (sw *Switch) reconnectToPeer(addr *NetAddress) { // --------------------------------------------------------------------- // Dialing -type privateAddr interface { - PrivateAddr() bool -} - // DialPeersAsync dials a list of peers asynchronously in random order. // Used to dial peers from config on startup or from unsafe-RPC (trusted sources). // It ignores NetAddressLookupError. However, if there are other errors, first diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index a0d4853493b..9e82e20bca1 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -105,6 +105,8 @@ func initSwitchFunc(i int, sw *Switch) *Switch { } func TestSwitches(t *testing.T) { + t.Parallel() + s1, s2 := MakeSwitchPair(t, initSwitchFunc) defer s1.Stop() defer s2.Stop() @@ -152,6 +154,8 @@ func assertMsgReceivedWithTimeout(t *testing.T, msgBytes []byte, channel byte, r } func TestSwitchFiltersOutItself(t *testing.T) { + t.Parallel() + s1 := MakeSwitch(cfg, 1, "127.0.0.1", "123.123.123", initSwitchFunc) // simulate s1 having a public IP by creating a remote peer with the same ID @@ -176,6 +180,8 @@ func TestSwitchFiltersOutItself(t *testing.T) { } func TestSwitchPeerFilter(t *testing.T) { + t.Parallel() + var ( filters = []PeerFilterFunc{ func(_ IPeerSet, _ Peer) error { return nil }, @@ -219,6 +225,8 @@ func TestSwitchPeerFilter(t *testing.T) { } func TestSwitchPeerFilterTimeout(t *testing.T) { + t.Parallel() + var ( filters = []PeerFilterFunc{ func(_ IPeerSet, _ Peer) error { @@ -260,6 +268,8 @@ func TestSwitchPeerFilterTimeout(t *testing.T) { } func TestSwitchPeerFilterDuplicate(t *testing.T) { + t.Parallel() + sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) sw.Start() defer sw.Stop() @@ -303,6 +313,8 @@ func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration) } func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) @@ -338,6 +350,8 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { } func TestSwitchStopPeerForError(t *testing.T) { + t.Parallel() + // make two connected switches sw1, sw2 := MakeSwitchPair(t, func(i int, sw *Switch) *Switch { return initSwitchFunc(i, sw) @@ -360,6 +374,8 @@ func TestSwitchStopPeerForError(t *testing.T) { } func TestSwitchReconnectsToOutboundPersistentPeer(t *testing.T) { + t.Parallel() + sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) err := sw.Start() require.NoError(t, err) @@ -405,6 +421,8 @@ func TestSwitchReconnectsToOutboundPersistentPeer(t *testing.T) { } func TestSwitchReconnectsToInboundPersistentPeer(t *testing.T) { + t.Parallel() + sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) err := sw.Start() require.NoError(t, err) @@ -430,6 +448,8 @@ func TestSwitchReconnectsToInboundPersistentPeer(t *testing.T) { } func TestSwitchDialPeersAsync(t *testing.T) { + t.Parallel() + if testing.Short() { return } @@ -460,6 +480,8 @@ func waitUntilSwitchHasAtLeastNPeers(sw *Switch, n int) { } func TestSwitchFullConnectivity(t *testing.T) { + t.Parallel() + switches := MakeConnectedSwitches(cfg, 3, initSwitchFunc, Connect2Switches) defer func() { for _, sw := range switches { @@ -475,6 +497,8 @@ func TestSwitchFullConnectivity(t *testing.T) { } func TestSwitchAcceptRoutine(t *testing.T) { + t.Parallel() + cfg.MaxNumInboundPeers = 5 // make switch @@ -547,6 +571,8 @@ func (errorTransport) Cleanup(Peer) { } func TestSwitchAcceptRoutineErrorCases(t *testing.T) { + t.Parallel() + sw := NewSwitch(cfg, errorTransport{FilterTimeoutError{}}) assert.NotPanics(t, func() { err := sw.Start() @@ -599,6 +625,8 @@ func (r *mockReactor) InitCalledBeforeRemoveFinished() bool { // see stopAndRemovePeer func TestFlappySwitchInitPeerIsNotCalledBeforeRemovePeer(t *testing.T) { + t.Parallel() + testutils.FilterStability(t, testutils.Flappy) // make reactor diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index 49ab9ac52e3..63b1c26e666 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -33,6 +33,8 @@ func newMultiplexTransport( } func TestTransportMultiplexConnFilter(t *testing.T) { + t.Parallel() + mt := newMultiplexTransport( emptyNodeInfo(), NodeKey{ @@ -87,6 +89,8 @@ func TestTransportMultiplexConnFilter(t *testing.T) { } func TestTransportMultiplexConnFilterTimeout(t *testing.T) { + t.Parallel() + mt := newMultiplexTransport( emptyNodeInfo(), NodeKey{ @@ -137,6 +141,8 @@ func TestTransportMultiplexConnFilterTimeout(t *testing.T) { } func TestTransportMultiplexAcceptMultiple(t *testing.T) { + t.Parallel() + mt := testSetupMultiplexTransport(t) laddr := NewNetAddress(mt.nodeKey.ID(), mt.listener.Addr()) @@ -212,6 +218,8 @@ func testDialer(dialAddr NetAddress, errc chan error) { } func TestFlappyTransportMultiplexAcceptNonBlocking(t *testing.T) { + t.Parallel() + testutils.FilterStability(t, testutils.Flappy) mt := testSetupMultiplexTransport(t) @@ -298,6 +306,8 @@ func TestFlappyTransportMultiplexAcceptNonBlocking(t *testing.T) { } func TestTransportMultiplexValidateNodeInfo(t *testing.T) { + t.Parallel() + mt := testSetupMultiplexTransport(t) errc := make(chan error) @@ -339,6 +349,8 @@ func TestTransportMultiplexValidateNodeInfo(t *testing.T) { } func TestTransportMultiplexRejectMismatchID(t *testing.T) { + t.Parallel() + mt := testSetupMultiplexTransport(t) errc := make(chan error) @@ -378,6 +390,8 @@ func TestTransportMultiplexRejectMismatchID(t *testing.T) { } func TestTransportMultiplexDialRejectWrongID(t *testing.T) { + t.Parallel() + mt := testSetupMultiplexTransport(t) var ( @@ -407,6 +421,8 @@ func TestTransportMultiplexDialRejectWrongID(t *testing.T) { } func TestTransportMultiplexRejectIncompatible(t *testing.T) { + t.Parallel() + mt := testSetupMultiplexTransport(t) errc := make(chan error) @@ -443,6 +459,8 @@ func TestTransportMultiplexRejectIncompatible(t *testing.T) { } func TestTransportMultiplexRejectSelf(t *testing.T) { + t.Parallel() + mt := testSetupMultiplexTransport(t) errc := make(chan error) @@ -482,6 +500,8 @@ func TestTransportMultiplexRejectSelf(t *testing.T) { } func TestTransportConnDuplicateIPFilter(t *testing.T) { + t.Parallel() + filter := ConnDuplicateIPFilter() if err := filter(nil, &testTransportConn{}, nil); err != nil { @@ -507,6 +527,8 @@ func TestTransportConnDuplicateIPFilter(t *testing.T) { } func TestTransportHandshake(t *testing.T) { + t.Parallel() + ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) diff --git a/tm2/pkg/p2p/upnp/upnp.go b/tm2/pkg/p2p/upnp/upnp.go index 40f2067e232..cd47ac35553 100644 --- a/tm2/pkg/p2p/upnp/upnp.go +++ b/tm2/pkg/p2p/upnp/upnp.go @@ -10,7 +10,7 @@ import ( "encoding/xml" "errors" "fmt" - "io/ioutil" + "io" "net" "net/http" "strconv" @@ -306,7 +306,7 @@ func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) { return } var envelope Envelope - data, err := ioutil.ReadAll(response.Body) + data, err := io.ReadAll(response.Body) if err != nil { return } @@ -363,7 +363,7 @@ func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int // TODO: check response to see if the port was forwarded // log.Println(message, response) // JAE: - // body, err := ioutil.ReadAll(response.Body) + // body, err := io.ReadAll(response.Body) // fmt.Println(string(body), err) mappedExternalPort = externalPort _ = response diff --git a/tm2/pkg/random/random_test.go b/tm2/pkg/random/random_test.go index e2d45533626..71e05e95e16 100644 --- a/tm2/pkg/random/random_test.go +++ b/tm2/pkg/random/random_test.go @@ -15,18 +15,24 @@ import ( ) func TestRandStr(t *testing.T) { + t.Parallel() + l := 243 s := RandStr(l) assert.Equal(t, l, len(s)) } func TestRandBytes(t *testing.T) { + t.Parallel() + l := 243 b := RandBytes(l) assert.Equal(t, l, len(b)) } func TestRandIntn(t *testing.T) { + t.Parallel() + n := 243 for i := 0; i < 100; i++ { x := RandIntn(n) @@ -76,6 +82,8 @@ func testThemAll() string { } func TestRngConcurrencySafety(t *testing.T) { + t.Parallel() + var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) diff --git a/tm2/pkg/sdk/auth/ante_test.go b/tm2/pkg/sdk/auth/ante_test.go index 2dc3ec08176..9c15cca18a2 100644 --- a/tm2/pkg/sdk/auth/ante_test.go +++ b/tm2/pkg/sdk/auth/ante_test.go @@ -59,6 +59,8 @@ func defaultAnteOptions() AnteOptions { // Test various error cases in the AnteHandler control flow. func TestAnteHandlerSigErrors(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() ctx := env.ctx @@ -107,6 +109,8 @@ func TestAnteHandlerSigErrors(t *testing.T) { // Test logic around account number checking with one signer and many signers. func TestAnteHandlerAccountNumbers(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) @@ -164,6 +168,8 @@ func TestAnteHandlerAccountNumbers(t *testing.T) { // Test logic around account number checking with many signers when BlockHeight is 0. func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) @@ -223,6 +229,8 @@ func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { // Test logic around sequence checking with one signer and many signers. func TestAnteHandlerSequences(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) @@ -300,6 +308,8 @@ func TestAnteHandlerSequences(t *testing.T) { // Test logic around fee deduction. func TestAnteHandlerFees(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() ctx := env.ctx @@ -341,6 +351,8 @@ func TestAnteHandlerFees(t *testing.T) { // Test logic around memo gas consumption. func TestAnteHandlerMemoGas(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) @@ -381,6 +393,8 @@ func TestAnteHandlerMemoGas(t *testing.T) { } func TestAnteHandlerMultiSigner(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) @@ -431,6 +445,8 @@ func TestAnteHandlerMultiSigner(t *testing.T) { } func TestAnteHandlerBadSignBytes(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) @@ -508,6 +524,8 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { } func TestAnteHandlerSetPubKey(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) @@ -560,6 +578,8 @@ func TestAnteHandlerSetPubKey(t *testing.T) { } func TestProcessPubKey(t *testing.T) { + t.Parallel() + env := setupTestEnv() ctx := env.ctx @@ -588,7 +608,10 @@ func TestProcessPubKey(t *testing.T) { {"pubkey doesn't match addr, simulate on", args{acc1, std.Signature{PubKey: priv2.PubKey()}, true}, false}, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + _, err := ProcessPubKey(tt.args.acc, tt.args.sig, tt.args.simulate) require.Equal(t, tt.wantErr, !err.IsOK()) }) @@ -596,6 +619,8 @@ func TestProcessPubKey(t *testing.T) { } func TestConsumeSignatureVerificationGas(t *testing.T) { + t.Parallel() + params := DefaultParams() msg := []byte{1, 2, 3, 4} @@ -625,7 +650,10 @@ func TestConsumeSignatureVerificationGas(t *testing.T) { {"unknown key", args{store.NewInfiniteGasMeter(), nil, nil, params}, 0, true}, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + res := DefaultSigVerificationGasConsumer(tt.args.meter, tt.args.sig, tt.args.pubkey, tt.args.params) if tt.shouldErr { @@ -671,6 +699,8 @@ func expectedGasCostByKeys(pubkeys []crypto.PubKey) int64 { } func TestCountSubkeys(t *testing.T) { + t.Parallel() + genPubKeys := func(n int) []crypto.PubKey { var ret []crypto.PubKey for i := 0; i < n; i++ { @@ -698,13 +728,18 @@ func TestCountSubkeys(t *testing.T) { {"multi level multikey", args{multiLevelMultiKey}, 11}, } for _, tt := range tests { - t.Run(tt.name, func(T *testing.T) { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tt.want, std.CountSubKeys(tt.args.pub)) }) } } func TestAnteHandlerSigLimitExceeded(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) @@ -742,6 +777,8 @@ func TestAnteHandlerSigLimitExceeded(t *testing.T) { } func TestEnsureSufficientMempoolFees(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() ctx := env.ctx.WithMinGasPrices( @@ -775,6 +812,8 @@ func TestEnsureSufficientMempoolFees(t *testing.T) { // Test custom SignatureVerificationGasConsumer func TestCustomSignatureVerificationGasConsumer(t *testing.T) { + t.Parallel() + // setup env := setupTestEnv() // setup an ante handler that only accepts PubKeyEd25519 diff --git a/tm2/pkg/sdk/auth/keeper_test.go b/tm2/pkg/sdk/auth/keeper_test.go index 3af74cf4a4c..d40d96cdb4b 100644 --- a/tm2/pkg/sdk/auth/keeper_test.go +++ b/tm2/pkg/sdk/auth/keeper_test.go @@ -9,6 +9,8 @@ import ( ) func TestAccountMapperGetSet(t *testing.T) { + t.Parallel() + env := setupTestEnv() addr := crypto.AddressFromPreimage([]byte("some-address")) @@ -38,6 +40,8 @@ func TestAccountMapperGetSet(t *testing.T) { } func TestAccountMapperRemoveAccount(t *testing.T) { + t.Parallel() + env := setupTestEnv() addr1 := crypto.AddressFromPreimage([]byte("addr1")) addr2 := crypto.AddressFromPreimage([]byte("addr2")) diff --git a/tm2/pkg/sdk/auth/test_common.go b/tm2/pkg/sdk/auth/test_common.go index a51c8576bad..ee4153184a4 100644 --- a/tm2/pkg/sdk/auth/test_common.go +++ b/tm2/pkg/sdk/auth/test_common.go @@ -19,33 +19,6 @@ type testEnv struct { bank BankKeeperI } -// moduleAccount defines an account for modules that holds coins on a pool -type moduleAccount struct { - *std.BaseAccount - name string `json:"name" yaml:"name"` // name of the module - permissions []string `json:"permissions" yaml"permissions"` // permissions of module account -} - -// HasPermission returns whether or not the module account has permission. -func (ma moduleAccount) HasPermission(permission string) bool { - for _, perm := range ma.permissions { - if perm == permission { - return true - } - } - return false -} - -// GetName returns the the name of the holder's module -func (ma moduleAccount) GetName() string { - return ma.name -} - -// GetPermissions returns permissions granted to the module account -func (ma moduleAccount) GetPermissions() []string { - return ma.permissions -} - func setupTestEnv() testEnv { db := dbm.NewMemDB() diff --git a/tm2/pkg/sdk/bank/handler_test.go b/tm2/pkg/sdk/bank/handler_test.go index fef4810c027..85fc68fc304 100644 --- a/tm2/pkg/sdk/bank/handler_test.go +++ b/tm2/pkg/sdk/bank/handler_test.go @@ -16,6 +16,8 @@ import ( ) func TestInvalidMsg(t *testing.T) { + t.Parallel() + h := NewHandler(BankKeeper{}) res := h.Process(sdk.NewContext(sdk.RunTxModeDeliver, nil, &bft.Header{ChainID: "test-chain"}, nil), tu.NewTestMsg()) require.False(t, res.IsOK()) @@ -23,6 +25,8 @@ func TestInvalidMsg(t *testing.T) { } func TestBalances(t *testing.T) { + t.Parallel() + env := setupTestEnv() h := NewHandler(env.bank) _, _, addr := tu.KeyTestPubAddr() @@ -51,6 +55,8 @@ func TestBalances(t *testing.T) { } func TestQuerierRouteNotFound(t *testing.T) { + t.Parallel() + env := setupTestEnv() h := NewHandler(env.bank) req := abci.RequestQuery{ diff --git a/tm2/pkg/sdk/bank/keeper_test.go b/tm2/pkg/sdk/bank/keeper_test.go index d4e230330d5..59b4c12689c 100644 --- a/tm2/pkg/sdk/bank/keeper_test.go +++ b/tm2/pkg/sdk/bank/keeper_test.go @@ -11,6 +11,8 @@ import ( ) func TestKeeper(t *testing.T) { + t.Parallel() + env := setupTestEnv() ctx := env.ctx @@ -88,6 +90,8 @@ func TestKeeper(t *testing.T) { } func TestBankKeeper(t *testing.T) { + t.Parallel() + env := setupTestEnv() ctx := env.ctx @@ -134,6 +138,8 @@ func TestBankKeeper(t *testing.T) { } func TestViewKeeper(t *testing.T) { + t.Parallel() + env := setupTestEnv() ctx := env.ctx view := NewViewKeeper(env.acck) diff --git a/tm2/pkg/sdk/baseapp_test.go b/tm2/pkg/sdk/baseapp_test.go index 2d130583885..f6c2038569a 100644 --- a/tm2/pkg/sdk/baseapp_test.go +++ b/tm2/pkg/sdk/baseapp_test.go @@ -80,6 +80,8 @@ func setupBaseApp(t *testing.T, options ...func(*BaseApp)) *BaseApp { } func TestMountStores(t *testing.T) { + t.Parallel() + app := setupBaseApp(t) // check both stores @@ -92,6 +94,8 @@ func TestMountStores(t *testing.T) { // Test that we can make commits and then reload old versions. // Test that LoadLatestVersion actually does. func TestLoadVersion(t *testing.T) { + t.Parallel() + pruningOpt := SetPruningOptions(store.PruneSyncable) name := t.Name() db := dbm.NewMemDB() @@ -139,6 +143,8 @@ func TestLoadVersion(t *testing.T) { } func TestAppVersionSetterGetter(t *testing.T) { + t.Parallel() + pruningOpt := SetPruningOptions(store.PruneSyncable) name := t.Name() db := dbm.NewMemDB() @@ -158,6 +164,8 @@ func TestAppVersionSetterGetter(t *testing.T) { } func TestLoadVersionInvalid(t *testing.T) { + t.Parallel() + pruningOpt := SetPruningOptions(store.PruneSyncable) name := t.Name() db := dbm.NewMemDB() @@ -198,6 +206,8 @@ func testLoadVersionHelper(t *testing.T, app *BaseApp, expectedHeight int64, exp } func TestOptionFunction(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() bap := newBaseApp("starting name", db, testChangeNameHelper("new name")) require.Equal(t, bap.name, "new name", "BaseApp should have had name changed via option function") @@ -211,6 +221,8 @@ func testChangeNameHelper(name string) func(*BaseApp) { // Test that Info returns the latest committed state. func TestInfo(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() app := newBaseApp(t.Name(), db) @@ -229,6 +241,8 @@ func TestInfo(t *testing.T) { } func TestBaseAppOptionSeal(t *testing.T) { + t.Parallel() + app := setupBaseApp(t) require.Panics(t, func() { @@ -258,6 +272,8 @@ func TestBaseAppOptionSeal(t *testing.T) { } func TestSetMinGasPrices(t *testing.T) { + t.Parallel() + minGasPrices, err := ParseGasPrices("5000stake/10gas") require.Nil(t, err) db := dbm.NewMemDB() @@ -266,6 +282,8 @@ func TestSetMinGasPrices(t *testing.T) { } func TestInitChainer(t *testing.T) { + t.Parallel() + name := t.Name() // keep the db and logger ourselves so // we can reload the same app later @@ -444,10 +462,6 @@ func (mch msgCounterHandler) Query(ctx Context, req abci.RequestQuery) abci.Resp panic("should not happen") } -func i2b(i int64) []byte { - return []byte{byte(i)} -} - func getIntFromStore(store store.Store, key []byte) int64 { bz := store.Get(key) if len(bz) == 0 { @@ -477,7 +491,7 @@ func incrementingCounter(t *testing.T, store store.Store, counterKey []byte, cou return } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- // Tx processing - CheckTx, DeliverTx, SimulateTx. // These tests use the serialized tx as input, while most others will use the // Check(), Deliver(), Simulate() methods directly. @@ -487,6 +501,8 @@ func incrementingCounter(t *testing.T, store store.Store, counterKey []byte, cou // on the store within a block, and that the CheckTx state // gets reset to the latest committed state during Commit func TestCheckTx(t *testing.T) { + t.Parallel() + // This ante handler reads the key and checks that the value matches the current counter. // This ensures changes to the kvstore persist across successive CheckTx. counterKey := []byte("counter-key") @@ -530,6 +546,8 @@ func TestCheckTx(t *testing.T) { // Test that successive DeliverTx can see each others' effects // on the store, both within and across blocks. func TestDeliverTx(t *testing.T) { + t.Parallel() + // test increments in the ante anteKey := []byte("ante-key") anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, mainKey, anteKey)) } @@ -566,14 +584,10 @@ func TestDeliverTx(t *testing.T) { } } -// Number of messages doesn't matter to CheckTx. -func TestMultiMsgCheckTx(t *testing.T) { - // TODO: ensure we get the same results - // with one message or many -} - // One call to DeliverTx should process all the messages, in order. func TestMultiMsgDeliverTx(t *testing.T) { + t.Parallel() + // increment the tx counter anteKey := []byte("ante-key") anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, mainKey, anteKey)) } @@ -633,17 +647,12 @@ func TestMultiMsgDeliverTx(t *testing.T) { require.Equal(t, int64(2), msgCounter2) } -// Interleave calls to Check and Deliver and ensure -// that there is no cross-talk. Check sees results of the previous Check calls -// and Deliver sees that of the previous Deliver calls, but they don't see eachother. -func TestConcurrentCheckDeliver(t *testing.T) { - // TODO -} - // Simulate a transaction that uses gas to compute the gas. // Simulate() and Query(".app/simulate", txBytes) should give // the same results. func TestSimulateTx(t *testing.T) { + t.Parallel() + gasConsumed := int64(5) anteOpt := func(bapp *BaseApp) { @@ -704,6 +713,8 @@ func TestSimulateTx(t *testing.T) { } func TestRunInvalidTransaction(t *testing.T) { + t.Parallel() + anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(func(ctx Context, tx Tx, simulate bool) (newCtx Context, res Result, abort bool) { newCtx = ctx @@ -779,6 +790,8 @@ func TestRunInvalidTransaction(t *testing.T) { // Test that transactions exceeding gas limits fail func TestTxGasLimits(t *testing.T) { + t.Parallel() + gasGranted := int64(10) anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(func(ctx Context, tx Tx, simulate bool) (newCtx Context, res Result, abort bool) { @@ -853,6 +866,8 @@ func TestTxGasLimits(t *testing.T) { // Test that transactions exceeding gas limits fail func TestMaxBlockGasLimits(t *testing.T) { + t.Parallel() + gasGranted := int64(10) anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(func(ctx Context, tx Tx, simulate bool) (newCtx Context, res Result, abort bool) { @@ -942,6 +957,8 @@ func TestMaxBlockGasLimits(t *testing.T) { } func TestBaseAppAnteHandler(t *testing.T) { + t.Parallel() + anteKey := []byte("ante-key") anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, mainKey, anteKey)) @@ -1011,6 +1028,8 @@ func TestBaseAppAnteHandler(t *testing.T) { } func TestGasConsumptionBadTx(t *testing.T) { + t.Parallel() + gasWanted := int64(5) anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(func(ctx Context, tx Tx, simulate bool) (newCtx Context, res Result, abort bool) { @@ -1074,6 +1093,8 @@ func TestGasConsumptionBadTx(t *testing.T) { // Test that we can only query from the latest committed state. func TestQuery(t *testing.T) { + t.Parallel() + key, value := []byte("hello"), []byte("goodbye") anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(func(ctx Context, tx Tx, simulate bool) (newCtx Context, res Result, abort bool) { diff --git a/tm2/pkg/service/service_test.go b/tm2/pkg/service/service_test.go index b3817d6f60a..adf14e0c539 100644 --- a/tm2/pkg/service/service_test.go +++ b/tm2/pkg/service/service_test.go @@ -16,6 +16,8 @@ func (testService) OnReset() error { } func TestBaseServiceWait(t *testing.T) { + t.Parallel() + ts := &testService{} ts.BaseService = *NewBaseService(nil, "TestService", ts) ts.Start() @@ -37,6 +39,8 @@ func TestBaseServiceWait(t *testing.T) { } func TestBaseServiceReset(t *testing.T) { + t.Parallel() + ts := &testService{} ts.BaseService = *NewBaseService(nil, "TestService", ts) ts.Start() diff --git a/tm2/pkg/std/coin.go b/tm2/pkg/std/coin.go index d5eb9bb1da0..75063320ad3 100644 --- a/tm2/pkg/std/coin.go +++ b/tm2/pkg/std/coin.go @@ -619,11 +619,9 @@ var ( // Denominations can be 3 ~ 16 characters long. reDnmString = `[a-z][a-z0-9]{2,15}` reAmt = `[[:digit:]]+` - reDecAmt = `[[:digit:]]*\.[[:digit:]]+` reSpc = `[[:space:]]*` reDnm = regexp.MustCompile(fmt.Sprintf(`^%s$`, reDnmString)) reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnmString)) - reDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reDecAmt, reSpc, reDnmString)) ) func validateDenom(denom string) error { diff --git a/tm2/pkg/std/coin_test.go b/tm2/pkg/std/coin_test.go index 923781ed412..8d9a9359b61 100644 --- a/tm2/pkg/std/coin_test.go +++ b/tm2/pkg/std/coin_test.go @@ -18,12 +18,16 @@ var ( // Coin tests func TestCoin(t *testing.T) { + t.Parallel() + require.Panics(t, func() { NewCoin(testDenom1, -1) }) require.Panics(t, func() { NewCoin(strings.ToUpper(testDenom1), 10) }) require.Equal(t, int64(5), NewCoin(testDenom1, 5).Amount) } func TestIsEqualCoin(t *testing.T) { + t.Parallel() + cases := []struct { inputOne Coin inputTwo Coin @@ -46,6 +50,8 @@ func TestIsEqualCoin(t *testing.T) { } func TestCoinIsValid(t *testing.T) { + t.Parallel() + cases := []struct { coin Coin expectPass bool @@ -66,6 +72,8 @@ func TestCoinIsValid(t *testing.T) { } func TestAddCoin(t *testing.T) { + t.Parallel() + cases := []struct { inputOne Coin inputTwo Coin @@ -88,6 +96,8 @@ func TestAddCoin(t *testing.T) { } func TestSubCoin(t *testing.T) { + t.Parallel() + cases := []struct { inputOne Coin inputTwo Coin @@ -120,6 +130,8 @@ func TestSubCoin(t *testing.T) { } func TestIsGTECoin(t *testing.T) { + t.Parallel() + cases := []struct { inputOne Coin inputTwo Coin @@ -142,6 +154,8 @@ func TestIsGTECoin(t *testing.T) { } func TestIsLTCoin(t *testing.T) { + t.Parallel() + cases := []struct { inputOne Coin inputTwo Coin @@ -167,6 +181,8 @@ func TestIsLTCoin(t *testing.T) { } func TestCoinIsZero(t *testing.T) { + t.Parallel() + coin := NewCoin(testDenom1, 0) res := coin.IsZero() require.True(t, res) @@ -180,6 +196,8 @@ func TestCoinIsZero(t *testing.T) { // Coins tests func TestIsZeroCoins(t *testing.T) { + t.Parallel() + cases := []struct { inputOne Coins expected bool @@ -198,6 +216,8 @@ func TestIsZeroCoins(t *testing.T) { } func TestEqualCoins(t *testing.T) { + t.Parallel() + cases := []struct { inputOne Coins inputTwo Coins @@ -224,6 +244,8 @@ func TestEqualCoins(t *testing.T) { } func TestAddCoins(t *testing.T) { + t.Parallel() + zero := int64(0) one := int64(1) two := int64(2) @@ -248,6 +270,8 @@ func TestAddCoins(t *testing.T) { } func TestSubCoins(t *testing.T) { + t.Parallel() + zero := int64(0) one := int64(1) two := int64(2) @@ -277,6 +301,8 @@ func TestSubCoins(t *testing.T) { } func TestCoins(t *testing.T) { + t.Parallel() + good := Coins{ {"gas", int64(1)}, {"mineral", int64(1)}, @@ -339,6 +365,8 @@ func TestCoins(t *testing.T) { } func TestCoinsGT(t *testing.T) { + t.Parallel() + one := int64(1) two := int64(2) @@ -351,6 +379,8 @@ func TestCoinsGT(t *testing.T) { } func TestCoinsLT(t *testing.T) { + t.Parallel() + one := int64(1) two := int64(2) @@ -366,6 +396,8 @@ func TestCoinsLT(t *testing.T) { } func TestCoinsLTE(t *testing.T) { + t.Parallel() + one := int64(1) two := int64(2) @@ -381,6 +413,8 @@ func TestCoinsLTE(t *testing.T) { } func TestParse(t *testing.T) { + t.Parallel() + one := int64(1) cases := []struct { @@ -413,6 +447,8 @@ func TestParse(t *testing.T) { } func TestSortCoins(t *testing.T) { + t.Parallel() + good := Coins{ NewCoin("gas", 1), NewCoin("mineral", 1), @@ -462,6 +498,8 @@ func TestSortCoins(t *testing.T) { } func TestAmountOf(t *testing.T) { + t.Parallel() + case0 := Coins{} case1 := Coins{ NewCoin("gold", 0), @@ -504,6 +542,8 @@ func TestAmountOf(t *testing.T) { } func TestCoinsIsAnyGTE(t *testing.T) { + t.Parallel() + one := int64(1) two := int64(2) @@ -524,6 +564,8 @@ func TestCoinsIsAnyGTE(t *testing.T) { } func TestCoinsIsAllGT(t *testing.T) { + t.Parallel() + one := int64(1) two := int64(2) @@ -544,6 +586,8 @@ func TestCoinsIsAllGT(t *testing.T) { } func TestCoinsIsAllGTE(t *testing.T) { + t.Parallel() + one := int64(1) two := int64(2) @@ -566,6 +610,8 @@ func TestCoinsIsAllGTE(t *testing.T) { } func TestNewCoins(t *testing.T) { + t.Parallel() + tenatom := NewCoin("atom", 10) tenbtc := NewCoin("btc", 10) zeroeth := NewCoin("eth", 0) @@ -582,7 +628,10 @@ func TestNewCoins(t *testing.T) { {"panic on dups", []Coin{tenatom, tenatom}, Coins{}, true}, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.wantPanic { require.Panics(t, func() { NewCoins(tt.coins...) }) return @@ -594,6 +643,8 @@ func TestNewCoins(t *testing.T) { } func TestCoinsIsAnyGT(t *testing.T) { + t.Parallel() + twoAtom := NewCoin("atom", 2) fiveAtom := NewCoin("atom", 5) threeEth := NewCoin("eth", 3) @@ -613,6 +664,8 @@ func TestCoinsIsAnyGT(t *testing.T) { } func TestFindDup(t *testing.T) { + t.Parallel() + abc := NewCoin("abc", 10) def := NewCoin("def", 10) ghi := NewCoin("ghi", 10) @@ -632,7 +685,10 @@ func TestFindDup(t *testing.T) { {"dup after first position", args{Coins{abc, def, def}}, 2}, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := findDup(tt.args.coins); got != tt.want { t.Errorf("findDup() = %v, want %v", got, tt.want) } @@ -641,6 +697,8 @@ func TestFindDup(t *testing.T) { } func TestMarshalJSONCoins(t *testing.T) { + t.Parallel() + testCases := []struct { name string input Coins @@ -652,7 +710,11 @@ func TestMarshalJSONCoins(t *testing.T) { } for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + bz, err := amino.MarshalJSON(tc.input) require.NoError(t, err) require.Equal(t, tc.strOutput, string(bz)) diff --git a/tm2/pkg/std/kvpair_test.go b/tm2/pkg/std/kvpair_test.go index d030da48053..5edf750e6dd 100644 --- a/tm2/pkg/std/kvpair_test.go +++ b/tm2/pkg/std/kvpair_test.go @@ -7,6 +7,8 @@ import ( ) func TestKVPairs(t *testing.T) { + t.Parallel() + kvs := KVPairs{ {Key: []byte("k2"), Value: []byte("")}, {Key: []byte("k1"), Value: []byte("2")}, @@ -32,6 +34,8 @@ func TestKVPairs(t *testing.T) { } func TestKI64Pairs(t *testing.T) { + t.Parallel() + kvs := KI64Pairs{ {Key: []byte("k2"), Value: 0}, {Key: []byte("k1"), Value: 2}, diff --git a/tm2/pkg/store/cache/store_test.go b/tm2/pkg/store/cache/store_test.go index 5daf83fcb61..adf122fc637 100644 --- a/tm2/pkg/store/cache/store_test.go +++ b/tm2/pkg/store/cache/store_test.go @@ -22,6 +22,8 @@ func keyFmt(i int) []byte { return bz(fmt.Sprintf("key%0.8d", i)) } func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) } func TestCacheStore(t *testing.T) { + t.Parallel() + mem := dbadapter.Store{dbm.NewMemDB()} st := cache.New(mem) @@ -65,12 +67,16 @@ func TestCacheStore(t *testing.T) { } func TestCacheStoreNoNilSet(t *testing.T) { + t.Parallel() + mem := dbadapter.Store{dbm.NewMemDB()} st := cache.New(mem) require.Panics(t, func() { st.Set([]byte("key"), nil) }, "setting a nil value should panic") } func TestCacheStoreNested(t *testing.T) { + t.Parallel() + mem := dbadapter.Store{dbm.NewMemDB()} st := cache.New(mem) @@ -100,6 +106,8 @@ func TestCacheStoreNested(t *testing.T) { } func TestCacheKVIteratorBounds(t *testing.T) { + t.Parallel() + st := newCacheStore() // set some items @@ -151,6 +159,8 @@ func TestCacheKVIteratorBounds(t *testing.T) { } func TestCacheKVReverseIteratorBounds(t *testing.T) { + t.Parallel() + st := newCacheStore() // set some items @@ -195,6 +205,8 @@ func TestCacheKVReverseIteratorBounds(t *testing.T) { } func TestCacheKVMergeIteratorBasics(t *testing.T) { + t.Parallel() + st := newCacheStore() // set and delete an item in the cache, iterator should be empty @@ -243,6 +255,8 @@ func TestCacheKVMergeIteratorBasics(t *testing.T) { } func TestCacheKVMergeIteratorDeleteLast(t *testing.T) { + t.Parallel() + st := newCacheStore() // set some items and write them @@ -269,6 +283,8 @@ func TestCacheKVMergeIteratorDeleteLast(t *testing.T) { } func TestCacheKVMergeIteratorDeletes(t *testing.T) { + t.Parallel() + st := newCacheStore() truth := dbm.NewMemDB() @@ -303,6 +319,8 @@ func TestCacheKVMergeIteratorDeletes(t *testing.T) { } func TestCacheKVMergeIteratorChunks(t *testing.T) { + t.Parallel() + st := newCacheStore() // Use the truth to check values on the merge iterator @@ -334,6 +352,8 @@ func TestCacheKVMergeIteratorChunks(t *testing.T) { } func TestCacheKVMergeIteratorRandom(t *testing.T) { + t.Parallel() + st := newCacheStore() truth := dbm.NewMemDB() diff --git a/tm2/pkg/store/gas/store_test.go b/tm2/pkg/store/gas/store_test.go index 70f1fc8ea7f..54729abf2ed 100644 --- a/tm2/pkg/store/gas/store_test.go +++ b/tm2/pkg/store/gas/store_test.go @@ -13,18 +13,14 @@ import ( "github.com/stretchr/testify/require" ) -func newGasKVStore() types.Store { - meter := types.NewGasMeter(10000) - mem := dbadapter.Store{dbm.NewMemDB()} - return gas.New(mem, meter, types.DefaultGasConfig()) -} - func bz(s string) []byte { return []byte(s) } func keyFmt(i int) []byte { return bz(fmt.Sprintf("key%0.8d", i)) } func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) } func TestGasKVStoreBasic(t *testing.T) { + t.Parallel() + mem := dbadapter.Store{dbm.NewMemDB()} meter := types.NewGasMeter(10000) st := gas.New(mem, meter, types.DefaultGasConfig()) @@ -37,6 +33,8 @@ func TestGasKVStoreBasic(t *testing.T) { } func TestGasKVStoreIterator(t *testing.T) { + t.Parallel() + mem := dbadapter.Store{dbm.NewMemDB()} meter := types.NewGasMeter(10000) st := gas.New(mem, meter, types.DefaultGasConfig()) @@ -61,6 +59,8 @@ func TestGasKVStoreIterator(t *testing.T) { } func TestGasKVStoreOutOfGasSet(t *testing.T) { + t.Parallel() + mem := dbadapter.Store{dbm.NewMemDB()} meter := types.NewGasMeter(0) st := gas.New(mem, meter, types.DefaultGasConfig()) @@ -68,6 +68,8 @@ func TestGasKVStoreOutOfGasSet(t *testing.T) { } func TestGasKVStoreOutOfGasIterator(t *testing.T) { + t.Parallel() + mem := dbadapter.Store{dbm.NewMemDB()} meter := types.NewGasMeter(20000) st := gas.New(mem, meter, types.DefaultGasConfig()) diff --git a/tm2/pkg/store/iavl/store_test.go b/tm2/pkg/store/iavl/store_test.go index 83afae91860..a29dbab096a 100644 --- a/tm2/pkg/store/iavl/store_test.go +++ b/tm2/pkg/store/iavl/store_test.go @@ -49,6 +49,8 @@ func newAlohaTree(t *testing.T, db dbm.DB) (*iavl.MutableTree, types.CommitID) { } func TestGetImmutable(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree, cID := newAlohaTree(t, db) store := UnsafeNewStore(tree, storeOptions(10, 10)) @@ -79,6 +81,8 @@ func TestGetImmutable(t *testing.T) { } func TestTestGetImmutableIterator(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree, cID := newAlohaTree(t, db) store := UnsafeNewStore(tree, storeOptions(10, 10)) @@ -102,6 +106,8 @@ func TestTestGetImmutableIterator(t *testing.T) { } func TestIAVLStoreGetSetHasDelete(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree, _ := newAlohaTree(t, db) iavlStore := UnsafeNewStore(tree, storeOptions(numRecent, storeEvery)) @@ -127,6 +133,8 @@ func TestIAVLStoreGetSetHasDelete(t *testing.T) { } func TestIAVLStoreNoNilSet(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree, _ := newAlohaTree(t, db) iavlStore := UnsafeNewStore(tree, storeOptions(numRecent, storeEvery)) @@ -134,6 +142,8 @@ func TestIAVLStoreNoNilSet(t *testing.T) { } func TestIAVLIterator(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree, _ := newAlohaTree(t, db) iavlStore := UnsafeNewStore(tree, storeOptions(numRecent, storeEvery)) @@ -207,6 +217,8 @@ func TestIAVLIterator(t *testing.T) { } func TestIAVLReverseIterator(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) iavlStore := UnsafeNewStore(tree, storeOptions(numRecent, storeEvery)) @@ -240,6 +252,8 @@ func TestIAVLReverseIterator(t *testing.T) { } func TestIAVLPrefixIterator(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) iavlStore := UnsafeNewStore(tree, storeOptions(numRecent, storeEvery)) @@ -302,6 +316,8 @@ func TestIAVLPrefixIterator(t *testing.T) { } func TestIAVLReversePrefixIterator(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) iavlStore := UnsafeNewStore(tree, storeOptions(numRecent, storeEvery)) @@ -368,6 +384,8 @@ func nextVersion(iavl *Store) { } func TestIAVLDefaultPruning(t *testing.T) { + t.Parallel() + // Expected stored / deleted version numbers for: // numRecent = 5, storeEvery = 3 states := []pruneState{ @@ -392,6 +410,8 @@ func TestIAVLDefaultPruning(t *testing.T) { } func TestIAVLAlternativePruning(t *testing.T) { + t.Parallel() + // Expected stored / deleted version numbers for: // numRecent = 3, storeEvery = 5 states := []pruneState{ @@ -442,6 +462,8 @@ func testPruning(t *testing.T, numRecent int64, storeEvery int64, states []prune } func TestIAVLNoPrune(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) iavlStore := UnsafeNewStore(tree, storeOptions(numRecent, int64(1))) @@ -457,6 +479,8 @@ func TestIAVLNoPrune(t *testing.T) { } func TestIAVLPruneEverything(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) iavlStore := UnsafeNewStore(tree, storeOptions(int64(0), int64(0))) @@ -475,6 +499,8 @@ func TestIAVLPruneEverything(t *testing.T) { } func TestIAVLStoreQuery(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree := iavl.NewMutableTree(db, cacheSize) iavlStore := UnsafeNewStore(tree, storeOptions(numRecent, storeEvery)) diff --git a/tm2/pkg/store/prefix/store_test.go b/tm2/pkg/store/prefix/store_test.go index 70f0eae05c8..6f70c5bd16f 100644 --- a/tm2/pkg/store/prefix/store_test.go +++ b/tm2/pkg/store/prefix/store_test.go @@ -88,6 +88,8 @@ func testPrefixStore(t *testing.T, baseStore types.Store, prefix []byte) { } func TestIAVLStorePrefix(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() tree := tiavl.NewMutableTree(db, cacheSize) iavlStore := iavl.UnsafeNewStore(tree, types.StoreOptions{ @@ -101,6 +103,8 @@ func TestIAVLStorePrefix(t *testing.T) { } func TestPrefixStoreNoNilSet(t *testing.T) { + t.Parallel() + meter := types.NewGasMeter(100000000) mem := dbadapter.Store{dbm.NewMemDB()} gasStore := gas.New(mem, meter, types.DefaultGasConfig()) @@ -108,6 +112,8 @@ func TestPrefixStoreNoNilSet(t *testing.T) { } func TestPrefixStoreIterate(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() baseStore := dbadapter.Store{db} prefix := []byte("test") @@ -135,6 +141,8 @@ func incFirstByte(bz []byte) { } func TestCloneAppend(t *testing.T) { + t.Parallel() + kvps := genRandomKVPairs() for _, kvp := range kvps { bz := cloneAppend(kvp.key, kvp.value) @@ -154,6 +162,8 @@ func TestCloneAppend(t *testing.T) { } func TestPrefixStoreIteratorEdgeCase(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() baseStore := dbadapter.Store{db} @@ -184,6 +194,8 @@ func TestPrefixStoreIteratorEdgeCase(t *testing.T) { } func TestPrefixStoreReverseIteratorEdgeCase(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() baseStore := dbadapter.Store{db} @@ -323,6 +335,8 @@ func checkNextPanics(t *testing.T, itr types.Iterator) { } func TestPrefixDBSimple(t *testing.T) { + t.Parallel() + store := mockStoreWithStuff() pstore := New(store, bz("key")) @@ -341,6 +355,8 @@ func TestPrefixDBSimple(t *testing.T) { } func TestPrefixDBIterator1(t *testing.T) { + t.Parallel() + store := mockStoreWithStuff() pstore := New(store, bz("key")) @@ -359,6 +375,8 @@ func TestPrefixDBIterator1(t *testing.T) { } func TestPrefixDBIterator2(t *testing.T) { + t.Parallel() + store := mockStoreWithStuff() pstore := New(store, bz("key")) @@ -369,6 +387,8 @@ func TestPrefixDBIterator2(t *testing.T) { } func TestPrefixDBIterator3(t *testing.T) { + t.Parallel() + store := mockStoreWithStuff() pstore := New(store, bz("key")) @@ -387,6 +407,8 @@ func TestPrefixDBIterator3(t *testing.T) { } func TestPrefixDBIterator4(t *testing.T) { + t.Parallel() + store := mockStoreWithStuff() pstore := New(store, bz("key")) @@ -397,6 +419,8 @@ func TestPrefixDBIterator4(t *testing.T) { } func TestPrefixDBReverseIterator1(t *testing.T) { + t.Parallel() + store := mockStoreWithStuff() pstore := New(store, bz("key")) @@ -415,6 +439,8 @@ func TestPrefixDBReverseIterator1(t *testing.T) { } func TestPrefixDBReverseIterator2(t *testing.T) { + t.Parallel() + store := mockStoreWithStuff() pstore := New(store, bz("key")) @@ -433,6 +459,8 @@ func TestPrefixDBReverseIterator2(t *testing.T) { } func TestPrefixDBReverseIterator3(t *testing.T) { + t.Parallel() + store := mockStoreWithStuff() pstore := New(store, bz("key")) @@ -443,6 +471,8 @@ func TestPrefixDBReverseIterator3(t *testing.T) { } func TestPrefixDBReverseIterator4(t *testing.T) { + t.Parallel() + store := mockStoreWithStuff() pstore := New(store, bz("key")) diff --git a/tm2/pkg/store/rootmulti/dbadapter.go b/tm2/pkg/store/rootmulti/dbadapter.go deleted file mode 100644 index 254d139a608..00000000000 --- a/tm2/pkg/store/rootmulti/dbadapter.go +++ /dev/null @@ -1,33 +0,0 @@ -package rootmulti - -import ( - "github.com/gnolang/gno/tm2/pkg/store/dbadapter" - "github.com/gnolang/gno/tm2/pkg/store/types" -) - -var commithash = []byte("FAKE_HASH") - -//---------------------------------------- -// commitDBStoreWrapper should only be used for simulation/debugging, -// as it doesn't compute any commit hash, and it cannot load older state. - -// Wrapper type for dbm.Db with implementation of KVStore -type commitDBStoreAdapter struct { - dbadapter.Store -} - -func (cdsa commitDBStoreAdapter) Commit() types.CommitID { - return types.CommitID{ - Version: -1, - Hash: commithash, - } -} - -func (cdsa commitDBStoreAdapter) LastCommitID() types.CommitID { - return types.CommitID{ - Version: -1, - Hash: commithash, - } -} - -func (cdsa commitDBStoreAdapter) SetPruning(_ types.PruningOptions) {} diff --git a/tm2/pkg/store/rootmulti/proof_test.go b/tm2/pkg/store/rootmulti/proof_test.go index 565ea59face..c79cbb07bd5 100644 --- a/tm2/pkg/store/rootmulti/proof_test.go +++ b/tm2/pkg/store/rootmulti/proof_test.go @@ -13,6 +13,8 @@ import ( ) func TestVerifyIAVLStoreQueryProof(t *testing.T) { + t.Parallel() + // Create main tree for testing. db := dbm.NewMemDB() opts := types.StoreOptions{ @@ -58,6 +60,8 @@ func TestVerifyIAVLStoreQueryProof(t *testing.T) { } func TestVerifyMultiStoreQueryProof(t *testing.T) { + t.Parallel() + // Create main tree for testing. db := dbm.NewMemDB() store := NewMultiStore(db) @@ -113,6 +117,8 @@ func TestVerifyMultiStoreQueryProof(t *testing.T) { } func TestVerifyMultiStoreQueryProofEmptyStore(t *testing.T) { + t.Parallel() + // Create main tree for testing. db := dbm.NewMemDB() store := NewMultiStore(db) @@ -142,6 +148,8 @@ func TestVerifyMultiStoreQueryProofEmptyStore(t *testing.T) { } func TestVerifyMultiStoreQueryProofAbsence(t *testing.T) { + t.Parallel() + // Create main tree for testing. db := dbm.NewMemDB() store := NewMultiStore(db) diff --git a/tm2/pkg/store/rootmulti/store_test.go b/tm2/pkg/store/rootmulti/store_test.go index e660f80c6bb..e6a04ee5ded 100644 --- a/tm2/pkg/store/rootmulti/store_test.go +++ b/tm2/pkg/store/rootmulti/store_test.go @@ -15,6 +15,8 @@ import ( ) func TestStoreType(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() store := NewMultiStore(db) store.MountStoreWithDB( @@ -22,6 +24,8 @@ func TestStoreType(t *testing.T) { } func TestStoreMount(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() store := NewMultiStore(db) @@ -37,6 +41,8 @@ func TestStoreMount(t *testing.T) { } func TestCacheMultiStoreWithVersion(t *testing.T) { + t.Parallel() + var db dbm.DB = dbm.NewMemDB() ms := newMultiStoreWithMounts(db) err := ms.LoadLatestVersion() @@ -74,6 +80,8 @@ func TestCacheMultiStoreWithVersion(t *testing.T) { } func TestHashStableWithEmptyCommit(t *testing.T) { + t.Parallel() + var db dbm.DB = dbm.NewMemDB() ms := newMultiStoreWithMounts(db) err := ms.LoadLatestVersion() @@ -98,6 +106,8 @@ func TestHashStableWithEmptyCommit(t *testing.T) { } func TestMultistoreCommitLoad(t *testing.T) { + t.Parallel() + var db dbm.DB = dbm.NewMemDB() store := newMultiStoreWithMounts(db) err := store.LoadLatestVersion() @@ -158,6 +168,8 @@ func TestMultistoreCommitLoad(t *testing.T) { } func TestParsePath(t *testing.T) { + t.Parallel() + _, _, err := parsePath("foo") require.Error(t, err) @@ -178,6 +190,8 @@ func TestParsePath(t *testing.T) { } func TestMultiStoreQuery(t *testing.T) { + t.Parallel() + db := dbm.NewMemDB() multi := newMultiStoreWithMounts(db) err := multi.LoadLatestVersion() diff --git a/tm2/pkg/store/types/gas_test.go b/tm2/pkg/store/types/gas_test.go index 7fef09227c5..410ba0b7e92 100644 --- a/tm2/pkg/store/types/gas_test.go +++ b/tm2/pkg/store/types/gas_test.go @@ -9,6 +9,8 @@ import ( ) func TestGasMeter(t *testing.T) { + t.Parallel() + cases := []struct { limit Gas usage []Gas @@ -46,6 +48,8 @@ func TestGasMeter(t *testing.T) { } func TestAddUint64Overflow(t *testing.T) { + t.Parallel() + testCases := []struct { a, b int64 result int64 diff --git a/tm2/pkg/strings/string_test.go b/tm2/pkg/strings/string_test.go index 1ec7b0d56be..55deac00b1f 100644 --- a/tm2/pkg/strings/string_test.go +++ b/tm2/pkg/strings/string_test.go @@ -9,6 +9,8 @@ import ( ) func TestStringInSlice(t *testing.T) { + t.Parallel() + assert.True(t, StringInSlice("a", []string{"a", "b", "c"})) assert.False(t, StringInSlice("d", []string{"a", "b", "c"})) assert.True(t, StringInSlice("", []string{""})) @@ -16,6 +18,8 @@ func TestStringInSlice(t *testing.T) { } func TestIsASCIIText(t *testing.T) { + t.Parallel() + notASCIIText := []string{ "", "\xC2", "\xC2\xA2", "\xFF", "\x80", "\xF0", "\n", "\t", } @@ -31,6 +35,8 @@ func TestIsASCIIText(t *testing.T) { } func TestASCIITrim(t *testing.T) { + t.Parallel() + assert.Equal(t, ASCIITrim(" "), "") assert.Equal(t, ASCIITrim(" a"), "a") assert.Equal(t, ASCIITrim("a "), "a") @@ -39,6 +45,8 @@ func TestASCIITrim(t *testing.T) { } func TestStringSliceEqual(t *testing.T) { + t.Parallel() + tests := []struct { a []string b []string diff --git a/tm2/pkg/timer/throttle_timer_test.go b/tm2/pkg/timer/throttle_timer_test.go index 2bee1dc0125..fb82fe39ff6 100644 --- a/tm2/pkg/timer/throttle_timer_test.go +++ b/tm2/pkg/timer/throttle_timer_test.go @@ -36,6 +36,8 @@ func (c *thCounter) Read() { } func TestThrottle(test *testing.T) { + test.Parallel() + assert := asrt.New(test) ms := 100