diff --git a/.gitattributes b/.gitattributes index a0c478da1fc..c22d136ec50 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ *.gno linguist-language=Go *.pb.go linguist-generated merge=ours -diff go.sum linguist-generated text +gnovm/stdlibs/native.go linguist-generated +gnovm/tests/stdlibs/native.go linguist-generated diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0906f3914da..639c6c186be 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,7 +3,6 @@ # Primary repo maintainers. * @gnolang/tech-staff - # Tendermint2 (Gno version). tm2/* @gnolang/tech-staff tm2/pkg @jaekwon @moul @@ -11,8 +10,8 @@ tm2/pkg @jaekwon @moul # Docs & Content. -docs/ @gnolang/tech-staff -*.md @gnolang/tech-staff +docs/ @gnolang/devrels @gnolang/tech-staff +misc/docusaurus/ @gnolang/devrels @gnolang/tech-staff # TODO: add non-tech people here. @@ -37,6 +36,3 @@ 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/codecov.yml b/.github/codecov.yml index 65609743a74..ecd223f0e84 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -52,3 +52,6 @@ flag_management: - name: gno.land paths: - gno.land + - name: misc + paths: + - misc diff --git a/.github/workflows/codegen.yml b/.github/workflows/codegen.yml new file mode 100644 index 00000000000..f941dd69855 --- /dev/null +++ b/.github/workflows/codegen.yml @@ -0,0 +1,36 @@ +name: code generation + +on: + push: + branches: [ "master" ] + pull_request: + paths: + - 'gnovm/stdlibs/**' + - 'gnovm/tests/stdlibs/**' + - 'misc/genstd' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + generated: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check generated files are up to date + run: | + go generate -x ./... + if [ "$(git status -s)" != "" ]; then + echo "command 'go generate' creates file that differ from git tree, please run 'go generate' and commit:" + git status -s + exit 1 + fi + diff --git a/.github/workflows/contribs.yml b/.github/workflows/contribs.yml index 939ac670710..e3bada1e195 100644 --- a/.github/workflows/contribs.yml +++ b/.github/workflows/contribs.yml @@ -24,6 +24,7 @@ jobs: - "1.21.x" program: - "gnomd" + - "gnodev" runs-on: ubuntu-latest timeout-minutes: 5 steps: diff --git a/.github/workflows/dependabot-tidy.yml b/.github/workflows/dependabot-tidy.yml new file mode 100644 index 00000000000..e13390fa831 --- /dev/null +++ b/.github/workflows/dependabot-tidy.yml @@ -0,0 +1,41 @@ +name: Dependabot Tidy Go Mods + +on: + pull_request: + paths: + - '.github/workflows/**' + - '**/go.mod' + - '**/go.sum' + +jobs: + tidy_go_mods: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: Tidy all Go mods + run: | + set -e + # Find all go.mod files + gomods=$(find . -type f -name go.mod) + + # Tidy each go.mod file + for modfile in $gomods; do + dir=$(dirname "$modfile") + + # Run go mod tidy in the directory + (cd "$dir" && go mod tidy -v) || exit 1 + done + + - name: Commit changes, if any + uses: stefanzweifel/git-auto-commit-action@v5 + with: + skip_dirty_check: false # Enable dirty check, and skip unnecessary committing + commit_message: "Run 'go mod tidy' via GitHub Actions" diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 3036720f6eb..52de6a09b80 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -28,7 +28,7 @@ jobs: run: mv .github/.fossa.yml . - name: Cache Coursier cache - uses: coursier/cache-action@v6.4.0 + uses: coursier/cache-action@v6.4.4 - name: Set up JDK 17 uses: coursier/setup-action@v1.3.0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000000..b874159473e --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,91 @@ +name: lint + +on: + push: + branches: [ "master" ] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: Lint + uses: golangci/golangci-lint-action@v3 + with: + # sync with misc/devdeps/go.mod + version: v1.54 + args: + --config=./.github/golangci.yml + fmt: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: Install make + run: sudo apt-get install -y make + + # prefill dependencies so that mod messages don't show up in make output + - name: Fetch dependencies + run: go mod download -modfile ./misc/devdeps/go.mod -x + + # inspired by: + # https://github.com/Jerome1337/gofmt-action/blob/d5eabd189843f1d568286a54578159978b7c0fb1/entrypoint.sh + - name: Check gofumpt + run: | + output="$(GOFMT_FLAGS=-l make -s fmt)" + if [ ! -z "$output" ]; then + echo "The following files are not properly formatted; run 'make fmt' to format them." + echo "$output" + exit 1 + else + echo 'Succeeded.' + fi + mod_tidy_check: + runs-on: ubuntu-latest + if: ${{ github.actor != 'dependabot[bot]' }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: Check go.mods + run: | + set -xe + # Find all go.mod files + gomods=$(find . -type f -name go.mod) + + # Calculate sums for all go.mod files + sums=$(sha256sum $gomods) + + # Iterate over each go.mod file + for modfile in $gomods; do + dir=$(dirname "$modfile") + + # Run go mod tidy in the directory + (cd "$dir" && go mod tidy -v) || exit 1 + done + + # Verify the sums + echo "$sums" | sha256sum -c diff --git a/.github/workflows/misc.yml b/.github/workflows/misc.yml index 64be9b4e829..9c666675fa4 100644 --- a/.github/workflows/misc.yml +++ b/.github/workflows/misc.yml @@ -1,65 +1,59 @@ +# tests the "misc" directory & tools +# (not meant for miscellaneous workflows) name: misc on: + pull_request: + paths: + - "misc/genstd/**.go" + - "misc/Makefile" + - ".github/workflows/misc.yml" + # Until the codecov issue is resolved, it's essential to run the tests for gnovm, tm2, misc, and gno.land concurrently. + - "gnovm/**" + - "tm2/**" + - "gno.land/**" + - "examples/**" + - ".github/workflows/**" push: branches: [ "master" ] - pull_request: concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: - lint: + build: + strategy: + fail-fast: false + matrix: + goversion: + - "1.21.x" + program: + - "genstd" runs-on: ubuntu-latest + timeout-minutes: 5 steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Install Go uses: actions/setup-go@v4 with: - go-version: 1.21.x - - - name: Lint - uses: golangci/golangci-lint-action@v3 - with: - # sync with misc/devdeps/go.mod - version: v1.54 - args: - --config=./.github/golangci.yml - fmt: - runs-on: ubuntu-latest - steps: + go-version: ${{ matrix.goversion }} - name: Checkout code uses: actions/checkout@v4 - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version: 1.21.x - - - name: Install make - run: sudo apt-get install -y make + - name: go install + working-directory: misc + run: go install ./${{ matrix.program }} - # prefill dependencies so that mod messages don't show up in make output - - name: Fetch dependencies - run: go mod download -modfile ./misc/devdeps/go.mod -x - - # inspired by: - # https://github.com/Jerome1337/gofmt-action/blob/d5eabd189843f1d568286a54578159978b7c0fb1/entrypoint.sh - - name: Check gofumpt - run: | - output="$(GOFMT_FLAGS=-l make -s fmt)" - if [ ! -z "$output" ]; then - echo "The following files are not properly formatted; run 'make fmt' to format them." - echo "$output" - exit 1 - else - echo 'Succeeded.' - fi - modtidy: + test: + strategy: + fail-fast: false + matrix: + goversion: + - "1.21.x" + args: + - _test.genstd runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout code uses: actions/checkout@v4 @@ -67,24 +61,20 @@ jobs: - name: Install Go uses: actions/setup-go@v4 with: - go-version: 1.21.x + go-version: ${{ matrix.goversion }} - - name: Check go.mods + - name: Test + working-directory: misc run: | - set -e - # Find all go.mod files - gomods=$(find . -type f -name go.mod) - - # Calculate sums for all go.mod files - sums=$(sha256sum $gomods) - - # Iterate over each go.mod file - for modfile in $gomods; do - dir=$(dirname "$modfile") - - # Run go mod tidy in the directory - (cd "$dir" && go mod tidy -v) || exit 1 - done - - # Verify the sums - echo "$sums" | sha256sum -c + export GOPATH=$HOME/go + export GOTEST_FLAGS="-v -p 1 -timeout=30m -coverprofile=coverage.out -covermode=atomic" + make ${{ matrix.args }} + + - if: runner.os == 'Linux' + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + name: misc + flags: misc,misc-${{matrix.args}},go-${{ matrix.goversion }} + files: ./misc/coverage.out + fail_ci_if_error: ${{ github.repository == 'gnolang/gno' }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c2bb8f9996..db0a989cb22 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -143,7 +143,7 @@ function! s:on_lsp_buffer_enabled() abort setlocal omnifunc=lsp#complete " Format on save autocmd BufWritePre LspDocumentFormatSync - " Some optionnal mappings + " Some optional mappings nmap i (lsp-hover) " Following mappings are not supported yet by gnols " nmap gd (lsp-definition) @@ -273,7 +273,7 @@ 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). +can find more documentation about them [on this guide](docs/testing-guide.md). ### Repository Structure diff --git a/Makefile b/Makefile index 358094e5c4a..80c9bef2ed7 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ test.components: $(MAKE) --no-print-directory -C gnovm test $(MAKE) --no-print-directory -C gno.land test $(MAKE) --no-print-directory -C examples test + $(MAKE) --no-print-directory -C misc test .PHONY: test.docker test.docker: diff --git a/README.md b/README.md index 6a59c4716b4..de50359c2ee 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ 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/explanation/proof-of-contribution.md)-based chain, backed by +[Proof of Contribution](./docs/concepts/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. @@ -20,7 +20,9 @@ consensus engine. 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) +> 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 @@ -45,6 +47,7 @@ We look forward to seeing your first PR! * [gnovm](./gnovm) - GnoVM and Gnolang. * [gno.land](./gno.land) - Gno.land blockchain and tools. * [tm2](./tm2) - Tendermint2. +* [contribs](./contribs) - Collection of enhanced tools for Gno. ## Socials & Contact @@ -67,7 +70,6 @@ We look forward to seeing your first PR! * [gnokey](./gno.land/cmd/gnokey) - key manipulation, also general interaction with gnoland * [gnoland](./gno.land/cmd/gnoland) - runs the blockchain node * [gnoweb](./gno.land/cmd/gnoweb) - serves gno website, along with user-defined content - * [logos](./misc/logos) - intended to be used as a browser Developer commands: diff --git a/contribs/Makefile b/contribs/Makefile index b816987b733..a7414771ffa 100644 --- a/contribs/Makefile +++ b/contribs/Makefile @@ -4,9 +4,10 @@ help: @cat Makefile | grep '^[a-z][^:]*:' | cut -d: -f1 | sort | sed 's/^/ /' .PHONY: install -install: install.gnomd +install: install.gnomd install.gnodev -install.gnomd:; cd gnomd && go install . +install.gnomd:; cd gnomd && go install . +install.gnodev:; $(MAKE) -C ./gnodev install .PHONY: clean clean: @@ -21,6 +22,15 @@ GOFMT_FLAGS ?= -w fmt: $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) . +.PHONY: tidy +tidy: + @for gomod in `find . -name go.mod`; do ( \ + dir=`dirname $$gomod`; \ + set -xe; \ + cd $$dir; \ + go mod tidy -v; \ + ); done + ######################################## # Test suite GOTEST_FLAGS ?= -v -p 1 -timeout=30m diff --git a/contribs/gnodev/Makefile b/contribs/gnodev/Makefile new file mode 100644 index 00000000000..23fb22a372d --- /dev/null +++ b/contribs/gnodev/Makefile @@ -0,0 +1,9 @@ +GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../../) + +GOBUILD_FLAGS := -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)" + +install: + go install $(GOBUILD_FLAGS) . + +build: + go build $(GOBUILD_FLAGS) -o build/gnodev ./cmd/gno diff --git a/contribs/gnodev/README.md b/contribs/gnodev/README.md new file mode 100644 index 00000000000..b3148c5dea3 --- /dev/null +++ b/contribs/gnodev/README.md @@ -0,0 +1,24 @@ +## `gnodev`: Your Gno Companion Tool + +`gnodev` is designed to be a robust and user-friendly tool in your realm package development journey, streamlining your workflow and enhancing productivity. + +### Synopsis +**gnodev** [**-minimal**] [**-no-watch**] [**PKGS_PATH ...**] + +### Features +- **In-Memory Node**: Automatically loads the **example** folder and any user-specified paths. +- **Web Interface Server**: Starts a gno.land web server on `:8888`. +- **Hot Reload**: Monitors the example packages folder and additional directories for file changes, reloading the package and restarting the node as needed. +- **State Maintenance**: Ensures the current state is maintained by reapplying all previous blocks. + +### Commands +- **H**: Display help information. +- **R**: Reload the node. +- **Ctrl+R**: Reset the current node state. +- **Ctrl+C**: Exit the command. + +### Example Folder Loading +The **example** package folder is loaded automatically. If working within this folder, you don't have to specify any additional paths to `gnodev`. Use `--minimal` to prevent this. + +### Installation +Run `make install` to install `gnodev`. diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod new file mode 100644 index 00000000000..32f70de42cc --- /dev/null +++ b/contribs/gnodev/go.mod @@ -0,0 +1,60 @@ +module github.com/gnolang/gno/contribs/gnodev + +go 1.20 + +replace github.com/gnolang/gno => ../.. + +require ( + github.com/fsnotify/fsnotify v1.7.0 + github.com/gnolang/gno v0.0.0-00010101000000-000000000000 + golang.org/x/term v0.15.0 +) + +require ( + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/btcsuite/btcd/btcutil v1.1.3 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/dgraph-io/badger/v3 v3.2103.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/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/mux v1.8.1 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/gorilla/sessions v1.2.1 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/gotuna/gotuna v0.6.0 // indirect + github.com/jaekwon/testify v1.6.1 // indirect + github.com/jmhodges/levigo v1.0.0 // indirect + github.com/klauspost/compress v1.12.3 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/linxGnu/grocksdb v1.8.5 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/peterbourgon/ff/v3 v3.4.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rs/cors v1.10.1 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect + go.etcd.io/bbolt v1.3.8 // indirect + go.opencensus.io v0.22.5 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum new file mode 100644 index 00000000000..bdc46387a5e --- /dev/null +++ b/contribs/gnodev/go.sum @@ -0,0 +1,299 @@ +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/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= +github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= +github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/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/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +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/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gnolang/goleveldb v0.0.9 h1:Q7rGko9oXMKtQA+Apeeed5a3sjba/mcDhzJGoTVLCKE= +github.com/gnolang/goleveldb v0.0.9/go.mod h1:Dz6p9bmpy/FBESTgduiThZt5mToVDipcHGzj/zUOo8E= +github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= +github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 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/csrf v1.7.0/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA= +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.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/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/jaekwon/testify v1.6.1/go.mod h1:Oun0RXIHI7osufabQ60i4Lqkj0GXLbqI1I7kgzBNm1U= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +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.5 h1:Okfk5B1h0ikCYdDM7Tc5yJUS8LTwAmMBq5IPWTmOLPs= +github.com/linxGnu/grocksdb v1.8.5/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml v1.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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= +go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-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.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +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-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/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 v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/contribs/gnodev/main.go b/contribs/gnodev/main.go new file mode 100644 index 00000000000..7b82e43ec7f --- /dev/null +++ b/contribs/gnodev/main.go @@ -0,0 +1,378 @@ +package main + +import ( + "context" + "flag" + "fmt" + "io" + "net" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/gnolang/gno/contribs/gnodev/pkg/dev" + gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev" + "github.com/gnolang/gno/contribs/gnodev/pkg/rawterm" + "github.com/gnolang/gno/gno.land/pkg/gnoweb" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/tm2/pkg/commands" + tmlog "github.com/gnolang/gno/tm2/pkg/log" + osm "github.com/gnolang/gno/tm2/pkg/os" +) + +const ( + NodeLogName = "Node" + WebLogName = "GnoWeb" + KeyPressLogName = "KeyPress" + HotReloadLogName = "HotReload" +) + +type devCfg struct { + webListenerAddr string + minimal bool + verbose bool + noWatch bool +} + +var defaultDevOptions = &devCfg{ + webListenerAddr: "127.0.0.1:8888", +} + +func main() { + cfg := &devCfg{} + + stdio := commands.NewDefaultIO() + cmd := commands.NewCommand( + commands.Metadata{ + Name: "gnodev", + ShortUsage: "gnodev [flags] [path ...]", + ShortHelp: "Runs an in-memory node and gno.land web server for development purposes.", + LongHelp: `The gnodev command starts an in-memory node and a gno.land web interface +primarily for realm package development. It automatically loads the example folder and any +additional specified paths.`, + }, + cfg, + func(_ context.Context, args []string) error { + return execDev(cfg, args, stdio) + }) + + if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil { + fmt.Fprintf(os.Stderr, "%+v\n", err) + os.Exit(1) + } +} +func (c *devCfg) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.webListenerAddr, + "web-listener", + defaultDevOptions.webListenerAddr, + "web server listening address", + ) + + fs.BoolVar( + &c.minimal, + "minimal", + defaultDevOptions.verbose, + "do not load example folder packages", + ) + + fs.BoolVar( + &c.verbose, + "verbose", + defaultDevOptions.verbose, + "verbose output when deving", + ) + + fs.BoolVar( + &c.noWatch, + "no-watch", + defaultDevOptions.noWatch, + "do not watch for files change", + ) + +} + +func execDev(cfg *devCfg, args []string, io commands.IO) error { + ctx, cancel := context.WithCancelCause(context.Background()) + defer cancel(nil) + + // guess root dir + gnoroot := gnoenv.RootDir() + + // Check and Parse packages + pkgpaths, err := parseArgsPackages(args) + if err != nil { + return fmt.Errorf("unable to parse package paths: %w", err) + } + + if !cfg.minimal { + examplesDir := filepath.Join(gnoroot, "examples") + pkgpaths = append(pkgpaths, examplesDir) + } + + // Setup Raw Terminal + rt, restore, err := setupRawTerm(io) + if err != nil { + return fmt.Errorf("unable to init raw term: %w", err) + } + defer restore() + + // Setup trap signal + osm.TrapSignal(func() { + restore() + cancel(nil) + }) + + // Setup Dev Node + // XXX: find a good way to export or display node logs + devNode, err := setupDevNode(ctx, rt, pkgpaths) + if err != nil { + return err + } + defer devNode.Close() + + rt.Taskf(NodeLogName, "Listener: %s\n", devNode.GetRemoteAddress()) + rt.Taskf(NodeLogName, "Default Address: %s\n", gnodev.DefaultCreator.String()) + rt.Taskf(NodeLogName, "Chain ID: %s\n", devNode.Config().ChainID()) + + // Setup packages watcher + pathChangeCh := make(chan []string, 1) + go func() { + defer close(pathChangeCh) + + cancel(runPkgsWatcher(ctx, cfg, devNode.ListPkgs(), pathChangeCh)) + }() + + // Setup GnoWeb listener + l, err := net.Listen("tcp", cfg.webListenerAddr) + if err != nil { + return fmt.Errorf("unable to listen to %q: %w", cfg.webListenerAddr, err) + } + defer l.Close() + + // Run GnoWeb server + go func() { + cancel(serveGnoWebServer(l, devNode, rt)) + }() + + rt.Taskf(WebLogName, "Listener: http://%s\n", l.Addr()) + + // GnoDev should be ready, run event loop + rt.Taskf("[Ready]", "for commands and help, press `h`") + + // Run the main event loop + return runEventLoop(ctx, cfg, rt, devNode, pathChangeCh) +} + +// XXX: Automatize this the same way command does +func printHelper(rt *rawterm.RawTerm) { + rt.Taskf("Helper", ` +Gno Dev Helper: + h, H Help - display this message + r, R Reload - Reload all packages to take change into account. + Ctrl+R Reset - Reset application state. + Ctrl+C Exit - Exit the application +`) +} + +func runEventLoop(ctx context.Context, + cfg *devCfg, + rt *rawterm.RawTerm, + dnode *dev.Node, + pathsCh <-chan []string, +) error { + nodeOut := rt.NamespacedWriter(NodeLogName) + keyOut := rt.NamespacedWriter(KeyPressLogName) + + keyPressCh := listenForKeyPress(keyOut, rt) + for { + var err error + + select { + case <-ctx.Done(): + return context.Cause(ctx) + case paths, ok := <-pathsCh: + if !ok { + return nil + } + + if cfg.verbose { + for _, path := range paths { + rt.Taskf(HotReloadLogName, "path %q has been modified", path) + } + } + + fmt.Fprintln(nodeOut, "Loading package updates...") + if err = dnode.UpdatePackages(paths...); err != nil { + checkForError(rt, err) + continue + } + + fmt.Fprintln(nodeOut, "Reloading...") + err = dnode.Reload(ctx) + checkForError(rt, err) + case key, ok := <-keyPressCh: + if !ok { + return nil + } + + if cfg.verbose { + fmt.Fprintf(keyOut, "<%s>\n", key.String()) + } + + switch key.Upper() { + case rawterm.KeyH: + printHelper(rt) + case rawterm.KeyR: + fmt.Fprintln(nodeOut, "Reloading all packages...") + checkForError(nodeOut, dnode.ReloadAll(ctx)) + case rawterm.KeyCtrlR: + fmt.Fprintln(nodeOut, "Reseting state...") + checkForError(nodeOut, dnode.Reset(ctx)) + case rawterm.KeyCtrlC: + return nil + default: + } + + // Listen for the next keypress + keyPressCh = listenForKeyPress(keyOut, rt) + } + } +} + +func runPkgsWatcher(ctx context.Context, cfg *devCfg, pkgs []gnomod.Pkg, changedPathsCh chan<- []string) error { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return fmt.Errorf("unable to watch files: %w", err) + } + + if cfg.noWatch { + // noop watcher, wait until context has been cancel + <-ctx.Done() + return ctx.Err() + } + + for _, pkg := range pkgs { + if err := watcher.Add(pkg.Dir); err != nil { + return fmt.Errorf("unable to watch %q: %w", pkg.Dir, err) + } + } + + const timeout = time.Millisecond * 500 + + var debounceTimer <-chan time.Time + pathList := []string{} + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case watchErr := <-watcher.Errors: + return fmt.Errorf("watch error: %w", watchErr) + case <-debounceTimer: + changedPathsCh <- pathList + // Reset pathList and debounceTimer + pathList = []string{} + debounceTimer = nil + case evt := <-watcher.Events: + if evt.Op != fsnotify.Write { + continue + } + + pathList = append(pathList, evt.Name) + debounceTimer = time.After(timeout) + } + } +} + +func setupRawTerm(io commands.IO) (rt *rawterm.RawTerm, restore func() error, err error) { + rt = rawterm.NewRawTerm() + + restore, err = rt.Init() + if err != nil { + return nil, nil, err + } + + // correctly format output for terminal + io.SetOut(commands.WriteNopCloser(rt)) + + return rt, restore, nil +} + +// setupDevNode initializes and returns a new DevNode. +func setupDevNode(ctx context.Context, rt *rawterm.RawTerm, pkgspath []string) (*gnodev.Node, error) { + nodeOut := rt.NamespacedWriter("Node") + + logger := tmlog.NewTMLogger(nodeOut) + logger.SetLevel(tmlog.LevelError) + return gnodev.NewDevNode(ctx, logger, pkgspath) +} + +// setupGnowebServer initializes and starts the Gnoweb server. +func serveGnoWebServer(l net.Listener, dnode *gnodev.Node, rt *rawterm.RawTerm) error { + var server http.Server + + webConfig := gnoweb.NewDefaultConfig() + webConfig.RemoteAddr = dnode.GetRemoteAddress() + + loggerweb := tmlog.NewTMLogger(rt.NamespacedWriter("GnoWeb")) + loggerweb.SetLevel(tmlog.LevelDebug) + + app := gnoweb.MakeApp(loggerweb, webConfig) + + server.ReadHeaderTimeout = 60 * time.Second + server.Handler = app.Router + + if err := server.Serve(l); err != nil { + return fmt.Errorf("unable to serve GnoWeb: %w", err) + } + + return nil +} + +func parseArgsPackages(args []string) (paths []string, err error) { + paths = make([]string, len(args)) + for i, arg := range args { + abspath, err := filepath.Abs(arg) + if err != nil { + return nil, fmt.Errorf("invalid path %q: %w", arg, err) + } + + ppath, err := gnomod.FindRootDir(abspath) + if err != nil { + return nil, fmt.Errorf("unable to find root dir of %q: %w", abspath, err) + } + + paths[i] = ppath + } + + return paths, nil +} + +func listenForKeyPress(w io.Writer, rt *rawterm.RawTerm) <-chan rawterm.KeyPress { + cc := make(chan rawterm.KeyPress, 1) + go func() { + defer close(cc) + key, err := rt.ReadKeyPress() + if err != nil { + fmt.Fprintf(w, "unable to read keypress: %s\n", err.Error()) + return + } + + cc <- key + }() + + return cc +} + +func checkForError(w io.Writer, err error) { + if err != nil { + fmt.Fprintf(w, "[ERROR] - %s\n", err.Error()) + return + } + + fmt.Fprintln(w, "[DONE]") +} diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go new file mode 100644 index 00000000000..6401f53e280 --- /dev/null +++ b/contribs/gnodev/pkg/dev/node.go @@ -0,0 +1,413 @@ +package dev + +import ( + "context" + "fmt" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/integration" + vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + 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/node" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/std" + //backup "github.com/gnolang/tx-archive/backup/client" + //restore "github.com/gnolang/tx-archive/restore/client" +) + +const gnoDevChainID = "tendermint_test" // XXX: this is hardcoded and cannot be change bellow + +// Node is not thread safe +type Node struct { + *node.Node + + logger log.Logger + pkgs PkgsMap // path -> pkg +} + +var ( + DefaultFee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) + DefaultCreator = crypto.MustAddressFromString(integration.DefaultAccount_Address) + DefaultBalance = []gnoland.Balance{ + { + Address: DefaultCreator, + Amount: std.MustParseCoins("10000000000000ugnot"), + }, + } +) + +func NewDevNode(ctx context.Context, logger log.Logger, pkgslist []string) (*Node, error) { + mpkgs, err := newPkgsMap(pkgslist) + if err != nil { + return nil, fmt.Errorf("unable map pkgs list: %w", err) + } + + txs, err := mpkgs.Load(DefaultCreator, DefaultFee, nil) + if err != nil { + return nil, fmt.Errorf("unable to load genesis packages: %w", err) + } + + // generate genesis state + genesis := gnoland.GnoGenesisState{ + Balances: DefaultBalance, + Txs: txs, + } + + node, err := newNode(logger, genesis) + if err != nil { + return nil, fmt.Errorf("unable to create the node: %w", err) + } + + if err := node.Start(); err != nil { + return nil, fmt.Errorf("unable to start node: %w", err) + } + + // Wait for readiness + select { + case <-gnoland.GetNodeReadiness(node): // ok + case <-ctx.Done(): + return nil, ctx.Err() + } + + return &Node{ + Node: node, + pkgs: mpkgs, + logger: logger, + }, nil +} + +func (d *Node) getLatestBlockNumber() uint64 { + return uint64(d.Node.BlockStore().Height()) +} + +func (d *Node) Close() error { + return d.Node.Stop() +} + +func (d *Node) ListPkgs() []gnomod.Pkg { + return d.pkgs.toList() +} + +func (d *Node) GetNodeReadiness() <-chan struct{} { + return gnoland.GetNodeReadiness(d.Node) +} + +func (d *Node) GetRemoteAddress() string { + return d.Node.Config().RPC.ListenAddress +} + +// UpdatePackages updates the currently known packages. It will be taken into +// consideration in the next reload of the node. +func (d *Node) UpdatePackages(paths ...string) error { + for _, path := range paths { + // List all packages from target path + pkgslist, err := gnomod.ListPkgs(path) + if err != nil { + return fmt.Errorf("failed to list gno packages for %q: %w", path, err) + } + + // Update or add package in the current known list. + for _, pkg := range pkgslist { + d.pkgs[pkg.Dir] = pkg + } + } + + return nil +} + +// Reset stops the node, if running, and reloads it with a new genesis state, +// effectively ignoring the current state. +func (d *Node) Reset(ctx context.Context) error { + // Stop the node if it's currently running. + if d.Node.IsRunning() { + if err := d.Node.Stop(); err != nil { + return fmt.Errorf("unable to stop the node: %w", err) + } + } + + // Generate a new genesis state based on the current packages + txs, err := d.pkgs.Load(DefaultCreator, DefaultFee, nil) + if err != nil { + return fmt.Errorf("unable to load pkgs: %w", err) + } + + genesis := gnoland.GnoGenesisState{ + Balances: DefaultBalance, + Txs: txs, + } + + // Reset the node with the new genesis state. + return d.reset(ctx, genesis) +} + +// ReloadAll updates all currently known packages and then reloads the node. +func (d *Node) ReloadAll(ctx context.Context) error { + pkgs := d.ListPkgs() + paths := make([]string, len(pkgs)) + for i, pkg := range pkgs { + paths[i] = pkg.Dir + } + + if err := d.UpdatePackages(paths...); err != nil { + return fmt.Errorf("unable to reload packages: %w", err) + } + + return d.Reload(ctx) +} + +// Reload saves the current state, stops the node if running, starts a new node, +// and re-apply previously saved state along with packages updated by `UpdatePackages`. +// If any transaction, including 'addpkg', fails, it will be ignored. +// Use 'Reset' to completely reset the node's state in case of persistent errors. +func (d *Node) Reload(ctx context.Context) error { + // Save the current state of the node. + state, err := d.saveState(ctx) + if err != nil { + return fmt.Errorf("unable to save state: %s", err.Error()) + } + + // Stop the node if it's currently running. + if d.Node.IsRunning() { + if err := d.Node.Stop(); err != nil { + return fmt.Errorf("unable to stop the node: %w", err) + } + } + + // Generate a new genesis state based on the current packages. + txs, err := d.pkgs.Load(DefaultCreator, DefaultFee, nil) + if err != nil { + return fmt.Errorf("unable to load pkgs: %w", err) + } + + genesis := gnoland.GnoGenesisState{ + Balances: DefaultBalance, + Txs: txs, + } + + // Reset the node with the new genesis state. + if err := d.reset(ctx, genesis); err != nil { + return fmt.Errorf("unable to reset the node: %w", err) + } + + // Attempt to resend transactions from the saved state. + for _, tx := range state { + if len(tx.Msgs) == 0 { // Skip empty transactions. + continue + } + + if err := d.SendTransaction(&tx); err != nil { + return fmt.Errorf("unable to send transaction: %w", err) + } + } + + return nil +} + +func (d *Node) reset(ctx context.Context, genesis gnoland.GnoGenesisState) error { + var err error + + // recoverError handles panics and converts them to errors. + recoverError := func() { + if r := recover(); r != nil { + panicErr, ok := r.(error) + if !ok { + panic(r) // Re-panic if not an error. + } + + err = panicErr + } + } + + createNode := func() { + defer recoverError() + + node, nodeErr := newNode(d.logger, genesis) + if nodeErr != nil { + err = fmt.Errorf("unable to create node: %w", nodeErr) + return + } + + if startErr := node.Start(); startErr != nil { + err = fmt.Errorf("unable to start the node: %w", startErr) + return + } + + d.Node = node + } + + // Execute node creation and handle any errors. + createNode() + if err != nil { + return err + } + + // Wait for the node to be ready + select { + case <-d.GetNodeReadiness(): // Ok + case <-ctx.Done(): + return ctx.Err() + } + + return err +} + +// GetBlockTransactions returns the transactions contained +// within the specified block, if any +func (d *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) { + b := d.Node.BlockStore().LoadBlock(int64(blockNum)) + txs := make([]std.Tx, len(b.Txs)) + for i, encodedTx := range b.Txs { + var tx std.Tx + if unmarshalErr := amino.Unmarshal(encodedTx, &tx); unmarshalErr != nil { + return nil, fmt.Errorf("unable to unmarshal amino tx, %w", unmarshalErr) + } + + txs[i] = tx + } + + return txs, nil +} + +// GetBlockTransactions returns the transactions contained +// within the specified block, if any +// GetLatestBlockNumber returns the latest block height from the chain +func (d *Node) GetLatestBlockNumber() (uint64, error) { + return d.getLatestBlockNumber(), nil +} + +// SendTransaction executes a broadcast sync send +// of the specified transaction to the chain +func (d *Node) SendTransaction(tx *std.Tx) error { + resCh := make(chan abci.Response, 1) + + aminoTx, err := amino.Marshal(tx) + if err != nil { + return fmt.Errorf("unable to marshal transaction to amino binary, %w", err) + } + + err = d.Node.Mempool().CheckTx(aminoTx, func(res abci.Response) { + resCh <- res + }) + if err != nil { + return fmt.Errorf("unable to check tx: %w", err) + } + + res := <-resCh + r := res.(abci.ResponseCheckTx) + if r.Error != nil { + return fmt.Errorf("unable to broadcast tx: %w\nLog: %s", r.Error, r.Log) + } + + return nil +} + +func (n *Node) saveState(ctx context.Context) ([]std.Tx, error) { + lastBlock := n.getLatestBlockNumber() + + state := make([]std.Tx, 0, int(lastBlock)) + var blocnum uint64 = 1 + for ; blocnum <= lastBlock; blocnum++ { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + txs, txErr := n.GetBlockTransactions(blocnum) + if txErr != nil { + return nil, fmt.Errorf("unable to fetch block transactions, %w", txErr) + } + + // Skip empty blocks + state = append(state, txs...) + } + + // override current state + return state, nil +} + +type PkgsMap map[string]gnomod.Pkg + +func newPkgsMap(paths []string) (PkgsMap, error) { + pkgs := make(map[string]gnomod.Pkg) + for _, path := range paths { + // list all packages from target path + pkgslist, err := gnomod.ListPkgs(path) + if err != nil { + return nil, fmt.Errorf("listing gno packages: %w", err) + } + + for _, pkg := range pkgslist { + if pkg.Dir == "" { + continue + } + + if _, ok := pkgs[pkg.Dir]; ok { + continue // skip + } + pkgs[pkg.Dir] = pkg + } + } + + // Filter out draft packages. + return pkgs, nil +} + +func (pm PkgsMap) toList() gnomod.PkgList { + list := make([]gnomod.Pkg, 0, len(pm)) + for _, pkg := range pm { + list = append(list, pkg) + } + return list +} + +func (pm PkgsMap) Load(creator bft.Address, fee std.Fee, deposit std.Coins) ([]std.Tx, error) { + pkgs := pm.toList() + + sorted, err := pkgs.Sort() + if err != nil { + return nil, fmt.Errorf("unable to sort pkgs: %w", err) + } + + nonDraft := sorted.GetNonDraftPkgs() + txs := []std.Tx{} + for _, pkg := range nonDraft { + // Open files in directory as MemPackage. + memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) + if err := memPkg.Validate(); err != nil { + return nil, fmt.Errorf("invalid package: %w", err) + } + + // 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 +} + +func newNode(logger log.Logger, genesis gnoland.GnoGenesisState) (*node.Node, error) { + rootdir := gnoenv.RootDir() + + nodeConfig := gnoland.NewDefaultInMemoryNodeConfig(rootdir) + nodeConfig.SkipFailingGenesisTxs = true + nodeConfig.Genesis.AppState = genesis + return gnoland.NewInMemoryNode(logger, nodeConfig) +} diff --git a/contribs/gnodev/pkg/rawterm/keypress.go b/contribs/gnodev/pkg/rawterm/keypress.go new file mode 100644 index 00000000000..20503476a9b --- /dev/null +++ b/contribs/gnodev/pkg/rawterm/keypress.go @@ -0,0 +1,55 @@ +package rawterm + +import ( + "fmt" + "unicode" +) + +type KeyPress byte + +// key representation +const ( + KeyNone KeyPress = 0 // None + KeyCtrlC KeyPress = '\x03' // Ctrl+C + KeyCtrlD KeyPress = '\x04' // Ctrl+D + KeyCtrlE KeyPress = '\x05' // Ctrl+E + KeyCtrlL KeyPress = '\x0c' // Ctrl+L + KeyCtrlO KeyPress = '\x0f' // Ctrl+O + KeyCtrlR KeyPress = '\x12' // Ctrl+R + KeyCtrlT KeyPress = '\x14' // Ctrl+T + + KeyH KeyPress = 'H' + KeyR KeyPress = 'R' +) + +func (k KeyPress) Upper() KeyPress { + return KeyPress(unicode.ToUpper(rune(k))) +} + +func (k KeyPress) String() string { + switch k { + case KeyNone: + return "Null" + case KeyCtrlC: + return "Ctrl+C" + case KeyCtrlD: + return "Ctrl+D" + case KeyCtrlE: + return "Ctrl+E" + case KeyCtrlL: + return "Ctrl+L" + case KeyCtrlO: + return "Ctrl+O" + case KeyCtrlR: + return "Ctrl+R" + case KeyCtrlT: + return "Ctrl+T" + default: + // For printable ASCII characters + if k > 0x20 && k < 0x7e { + return fmt.Sprintf("%c", k) + } + + return fmt.Sprintf("Unknown (0x%02x)", byte(k)) + } +} diff --git a/contribs/gnodev/pkg/rawterm/rawterm.go b/contribs/gnodev/pkg/rawterm/rawterm.go new file mode 100644 index 00000000000..8f29e88fc92 --- /dev/null +++ b/contribs/gnodev/pkg/rawterm/rawterm.go @@ -0,0 +1,180 @@ +package rawterm + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" + "sync" + + "golang.org/x/term" +) + +var ( + CRLF = []byte{'\r', '\n'} +) + +// rawTerminal wraps an io.Writer, converting \n to \r\n +type RawTerm struct { + syncWriter sync.Mutex + + fsin *os.File + reader io.Reader + taskWriter TaskWriter +} + +func NewRawTerm() *RawTerm { + return &RawTerm{ + fsin: os.Stdin, + reader: os.Stdin, + taskWriter: &rawTaskWriter{os.Stdout}, + } +} + +func (rt *RawTerm) Init() (restore func() error, err error) { + fd := int(rt.fsin.Fd()) + oldstate, err := term.MakeRaw(fd) + if err != nil { + return nil, fmt.Errorf("unable to init raw term: %w", err) + } + + rt.reader = rt.fsin + rt.taskWriter = &columnTaskWriter{os.Stdout} + return func() error { + return term.Restore(fd, oldstate) + }, nil +} + +func (rt *RawTerm) Taskf(task string, format string, args ...interface{}) (n int, err error) { + format = strings.TrimSpace(format) + if len(args) > 0 { + str := fmt.Sprintf(format, args...) + return rt.taskWriter.WriteTask(task, []byte(str+"\n")) + } + + return rt.taskWriter.WriteTask(task, []byte(format+"\n")) +} + +func (rt *RawTerm) Write(buf []byte) (n int, err error) { + rt.syncWriter.Lock() + defer rt.syncWriter.Unlock() + + return rt.taskWriter.Write(buf) +} + +func (rt *RawTerm) WriteTask(name string, buf []byte) (n int, err error) { + rt.syncWriter.Lock() + defer rt.syncWriter.Unlock() + + return rt.taskWriter.WriteTask(name, buf) +} + +func (rt *RawTerm) NamespacedWriter(namepsace string) io.Writer { + return &namespaceWriter{namepsace, rt} +} + +func (rt *RawTerm) read(buf []byte) (n int, err error) { + return rt.fsin.Read(buf) +} + +func (rt *RawTerm) ReadKeyPress() (KeyPress, error) { + buf := make([]byte, 1) + if _, err := rt.read(buf); err != nil { + return KeyNone, err + } + + return KeyPress(buf[0]), nil +} + +type namespaceWriter struct { + namespace string + writer TaskWriter +} + +func (r *namespaceWriter) Write(buf []byte) (n int, err error) { + return r.writer.WriteTask(r.namespace, buf) +} + +type TaskWriter interface { + io.Writer + WriteTask(task string, buf []byte) (n int, err error) +} + +type columnTaskWriter struct { + writer io.Writer +} + +func (r *columnTaskWriter) Write(buf []byte) (n int, err error) { + return r.WriteTask("", buf) +} + +func (r *columnTaskWriter) WriteTask(left string, buf []byte) (n int, err error) { + var nline int + for nline = 0; len(buf) > 0; nline++ { + i := bytes.IndexByte(buf, '\n') + todo := len(buf) + if i >= 0 { + todo = i + } + + var nn int + switch { + case nline == 0, left == "": // first line or left side is empty + nn, err = r.writeColumnLine(left, buf[:todo]) + case i < 0 || i+1 == len(buf): // last line + nn, err = r.writeColumnLine(" └─", buf[:todo]) + default: // middle lines + nn, err = r.writeColumnLine(" │", buf[:todo]) + } + + n += nn + if err != nil { + return n, err + } + buf = buf[todo:] + + if i >= 0 { // always jump a line on the last line + if _, err = r.writer.Write(CRLF); err != nil { + return n, err + } + n++ + buf = buf[1:] + } + } + + return +} + +func (r *columnTaskWriter) writeColumnLine(left string, line []byte) (n int, err error) { + // Write left column + if n, err = fmt.Fprintf(r.writer, "%-15s | ", left); err != nil { + return n, err + } + + // Write left line + var nn int + nn, err = r.writer.Write(line) + n += nn + + return +} + +type rawTaskWriter struct { + writer io.Writer +} + +func (r *rawTaskWriter) Write(buf []byte) (n int, err error) { + return r.writer.Write(buf) +} + +func (r *rawTaskWriter) WriteTask(task string, buf []byte) (n int, err error) { + if task != "" { + n, err = r.writer.Write([]byte(task + ": ")) + } + + var nn int + nn, err = r.writer.Write(buf) + n += nn + return +} diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 4f832dc3359..22f07943f39 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -15,7 +15,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect - github.com/cockroachdb/apd v1.1.0 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/danieljoos/wincred v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index 21b2d9a6f9b..cc1f4dfed88 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -35,8 +35,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf 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/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/docs/explanation/from-go-to-gno.md b/docs/concepts/from-go-to-gno.md similarity index 100% rename from docs/explanation/from-go-to-gno.md rename to docs/concepts/from-go-to-gno.md diff --git a/docs/explanation/gno-language.md b/docs/concepts/gno-language.md similarity index 100% rename from docs/explanation/gno-language.md rename to docs/concepts/gno-language.md diff --git a/docs/explanation/gno-modules.md b/docs/concepts/gno-modules.md similarity index 97% rename from docs/explanation/gno-modules.md rename to docs/concepts/gno-modules.md index 30f1de7b714..970dd16644c 100644 --- a/docs/explanation/gno-modules.md +++ b/docs/concepts/gno-modules.md @@ -4,7 +4,7 @@ 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. +The packages and realms containing `gno.mod` file can be referred 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? diff --git a/docs/explanation/gno-test.md b/docs/concepts/gno-test.md similarity index 100% rename from docs/explanation/gno-test.md rename to docs/concepts/gno-test.md diff --git a/docs/explanation/gno-tooling/cli/gno.md b/docs/concepts/gno-tooling/cli/gno.md similarity index 100% rename from docs/explanation/gno-tooling/cli/gno.md rename to docs/concepts/gno-tooling/cli/gno.md diff --git a/docs/explanation/gno-tooling/cli/gnofaucet.md b/docs/concepts/gno-tooling/cli/gnofaucet.md similarity index 100% rename from docs/explanation/gno-tooling/cli/gnofaucet.md rename to docs/concepts/gno-tooling/cli/gnofaucet.md diff --git a/docs/explanation/gno-tooling/cli/gnokey.md b/docs/concepts/gno-tooling/cli/gnokey.md similarity index 90% rename from docs/explanation/gno-tooling/cli/gnokey.md rename to docs/concepts/gno-tooling/cli/gnokey.md index dffee356509..72432ebfc45 100644 --- a/docs/explanation/gno-tooling/cli/gnokey.md +++ b/docs/concepts/gno-tooling/cli/gnokey.md @@ -208,13 +208,17 @@ gnokey maketx call \ #### **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. | - +| 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 | An argument of the function being called. Can be used multiple times in a single `call` command to accommodate possible multiple function arguments. | + +:::info +Currently, only primitive types are supported as `-args` parameters. This limitation will be addressed in the future. +Alternatively, see how `maketx run` works. +::: ### `send` diff --git a/docs/explanation/gno-tooling/cli/gnoland.md b/docs/concepts/gno-tooling/cli/gnoland.md similarity index 100% rename from docs/explanation/gno-tooling/cli/gnoland.md rename to docs/concepts/gno-tooling/cli/gnoland.md diff --git a/docs/explanation/gno-tooling/cli/tm2txsync.md b/docs/concepts/gno-tooling/cli/tm2txsync.md similarity index 100% rename from docs/explanation/gno-tooling/cli/tm2txsync.md rename to docs/concepts/gno-tooling/cli/tm2txsync.md diff --git a/docs/explanation/gnovm.md b/docs/concepts/gnovm.md similarity index 100% rename from docs/explanation/gnovm.md rename to docs/concepts/gnovm.md diff --git a/docs/explanation/packages.md b/docs/concepts/packages.md similarity index 100% rename from docs/explanation/packages.md rename to docs/concepts/packages.md diff --git a/docs/explanation/proof-of-contribution.md b/docs/concepts/proof-of-contribution.md similarity index 100% rename from docs/explanation/proof-of-contribution.md rename to docs/concepts/proof-of-contribution.md diff --git a/docs/explanation/realms.md b/docs/concepts/realms.md similarity index 100% rename from docs/explanation/realms.md rename to docs/concepts/realms.md diff --git a/docs/explanation/tendermint2.md b/docs/concepts/tendermint2.md similarity index 100% rename from docs/explanation/tendermint2.md rename to docs/concepts/tendermint2.md diff --git a/docs/getting-started/browsing-gno-source-code.md b/docs/getting-started/browsing-gno-source-code.md index 24e7c173f95..3e49fd1f57e 100644 --- a/docs/getting-started/browsing-gno-source-code.md +++ b/docs/getting-started/browsing-gno-source-code.md @@ -6,8 +6,8 @@ id: 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 +In this tutorial, you will learn how to browse deployed Gno [Realms](../concepts/realms.md) +and [Packages](../concepts/packages.md). Additionally, you will understand how the `Render` method is utilized to achieve Realm state visibility. ## Prerequisites @@ -48,7 +48,7 @@ We should be able to access the website locally on http://127.0.0.1:8888/. 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. +the [Packages](../concepts/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`. @@ -68,7 +68,7 @@ In contrast to Packages, Realms in Gno.land usually have names resembling `gno.l 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. +check out the [Realms](../concepts/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`. diff --git a/docs/how-to-guides/creating-grc20.md b/docs/how-to-guides/creating-grc20.md index 437294882c3..50a1af51d05 100644 --- a/docs/how-to-guides/creating-grc20.md +++ b/docs/how-to-guides/creating-grc20.md @@ -6,7 +6,7 @@ id: creating-grc20 ## 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. +This guide shows you how to write a simple _GRC20_ Smart Contract, or rather a [Realm](../concepts/realms.md), in [Gno (Gnolang)](../concepts/gno-language.md). For actually deploying the Realm, please see the [deployment](deploy.md) guide. Our _GRC20_ Realm will have the following functionality: @@ -153,7 +153,7 @@ Detailing what is happening in the above code: - 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). + method and how it's used [here](../concepts/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 diff --git a/docs/how-to-guides/creating-grc721.md b/docs/how-to-guides/creating-grc721.md index e4ac695a71b..f8049e8a135 100644 --- a/docs/how-to-guides/creating-grc721.md +++ b/docs/how-to-guides/creating-grc721.md @@ -6,8 +6,8 @@ id: creating-grc721 ## 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 +This guide shows you how to write a simple _GRC721_ Smart Contract, or rather a [Realm](../concepts/realms.md), +in [Gno (Gnolang)](../concepts/gno-language.md). For actually deploying the Realm, please see the [deployment](deploy.md) guide. Our _GRC721_ Realm will have the following functionality: @@ -186,7 +186,7 @@ Detailing what is happening in the above code: 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). + this `Render` method and how it's used [here](../concepts/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. diff --git a/docs/how-to-guides/simple-contract.md b/docs/how-to-guides/simple-contract.md index 0e77a6a75e1..604dec044eb 100644 --- a/docs/how-to-guides/simple-contract.md +++ b/docs/how-to-guides/simple-contract.md @@ -6,8 +6,8 @@ id: simple-contract ## 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 +This guide shows you how to write a simple _Counter_ Smart Contract, or rather a [Realm](../concepts/realms.md), +in [Gno (Gnolang)](../concepts/gno-language.md). For actually deploying the Realm, please see the [deployment](deploy.md) guide. Our _Counter_ Realm will have the following functionality: @@ -50,7 +50,7 @@ cd counter-app mkdir r ``` -Alternatively, if we were writing a [Gno Package](../explanation/packages.md), we would denote this directory name +Alternatively, if we were writing a [Gno Package](../concepts/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`: @@ -115,7 +115,7 @@ There are a few things happening here, so let's dissect them: - `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). + method and how it's used [here](../concepts/realms.md). :::info A note on constructors Gno Realms support a concept taken from other programming languages - _constructors_. diff --git a/docs/how-to-guides/simple-library.md b/docs/how-to-guides/simple-library.md index 3ed4ad11754..10b9c2024cb 100644 --- a/docs/how-to-guides/simple-library.md +++ b/docs/how-to-guides/simple-library.md @@ -8,7 +8,7 @@ id: simple-library 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). +intricacies of Packages, please see the [Packages reference](../concepts/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. diff --git a/docs/overview.md b/docs/overview.md index 50e07442ea0..4a335abd3e7 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -43,3 +43,11 @@ In comparison to Ethereum, Gno.land offers distinct advantages: 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. + +## Gno.land Documentation Overview + +Gno.land's documentation adopts the [Diataxis](https://diataxis.fr/) framework, ensuring structured and predictable content. It includes: +- Conceptual explanations, offering context and usage insights. +- Detailed reference sections with implementation specifics. +- Tutorials aimed at beginners to build fundamental skills in using Gno.land. +- Concise how-to guides for specific technical tasks. diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md index 42ebe3c8ff7..71f46adf836 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/reference/go-gno-compatibility.md @@ -83,7 +83,7 @@ Legend: 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 +* `todo`: the standard library 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. diff --git a/docs/reference/rpc-endpoints.md b/docs/reference/rpc-endpoints.md index 790e4c49142..896416c1760 100644 --- a/docs/reference/rpc-endpoints.md +++ b/docs/reference/rpc-endpoints.md @@ -108,7 +108,7 @@ Call with the /consensus\_params path to check the consensus algorithm parameter | `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. | +| `consensus_params.Validator` | Object | The validator parameters. | ## Get Consensus State diff --git a/docs/reference/standard-library.md b/docs/reference/standard-library.md index 71fad4943e6..8ed6ade4cb6 100644 --- a/docs/reference/standard-library.md +++ b/docs/reference/standard-library.md @@ -2,67 +2,193 @@ 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" +# Standard Libraries + +Gno comes with a set of standard libraries which are included whenever you +execute Gno code. These are distinguishable from imports of packages from the +chain by not referring to a "domain" as the first part of their import path. For +instance, `import "encoding/binary"` refers to a standard library, while +`import "gno.land/p/demo/avl"` refers to an on-chain package. + +Standard libraries packages follow the same semantics as on-chain packages (ie. +they don't persist state like realms do) and come as a part of the Gno +programming language rather than with the Gno.land chain. + +Many standard libraries are near-identical copies of the equivalent Go standard +libraries; in fact, you can check the current status of implementation of each +Go standard library on [Go\<\>Gno compatibility](go-gno-compatibility.md). + +## Gathering documentation + +At the time being, there is no "list" of the available standard libraries +available from Gno tooling or documentation, but you can obtain a list of all +the available packages with the following commands: + +```console +$ cd gnovm/stdlibs # go to correct directory +$ find -type d +./testing +./math +./crypto +./crypto/chacha20 +./crypto/chacha20/chacha +./crypto/chacha20/rand +./crypto/sha256 +./crypto/cipher +... ``` -Let's explore some of the most commonly used modules in the library. - -## `stdshim` - -### `banker.gno` +All of the packages have automatic, generated documentation through the use of +`gno doc`, which has similar functionality and features to `go doc`: -A library for manipulating `Coins`. Interfaces that must be implemented when using this library are as follows: +```console +$ gno doc encoding/binary +package binary // import "encoding/binary" -[embedmd]:# (../assets/reference/standard-library/std-1.gno go) -```go -// returns the list of coins owned by the address -GetCoins(addr Address) (dst Coins) +Package binary implements simple translation between numbers and byte sequences +and encoding and decoding of varints. -// sends coins from one address to another -SendCoins(from, to Address, amt Coins) +[...] -// returns the total supply of the coin -TotalCoin(denom string) int64 +var BigEndian bigEndian +var LittleEndian littleEndian +type AppendByteOrder interface{ ... } +type ByteOrder interface{ ... } +$ gno doc -u -src encoding/binary littleEndian.AppendUint16 +package binary // import "encoding/binary" -// issues coins to the address -IssueCoin(addr Address, denom string, amount int64) - -// burns coins from the address -RemoveCoin(addr Address, denom string, amount int64) +func (littleEndian) AppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} ``` -### `coins.gno` +`gno doc` will work automatically when used within the Gno repository or any +repository which has a `go.mod` dependency on `github.com/gnolang/gno`, which +can be a simple way to set up your Gno repositories to automatically support +`gno` commands (aside from `doc`, also `test`, `run`, etc.). -A library that declares structs for expressing `Coins`. The struct looks like the following: +Another alternative is setting your environment variable `GNOROOT` to point to +where you cloned the Gno repository. You can set this in your `~/.profile` file +to be automatically set up in your console: -[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 -} +```sh +export GNOROOT=$HOME/gno ``` -### `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`. +## Test standard libraries + +There are some additional standard library functions and packages which are +currently available only in `_test.gno` and `_filetest.gno` files. At the time +of writing, these are only some additions in the `std` package to support +changing some values in test functions. + +`gno doc` currently doesn't support reading from the test standard libraries, +though support is planned to be added. For now, you can inspect the directory +`gnovm/tests/stdlibs`. + +## Adding new standard libraries + +New standard libraries may be added by simply creating a new directory (whose +path relative to the `stdlibs` directory will be the import path used in Gno +programs). Following that, the suggested approach for adding a Go standard +library is to copy the original files from the Go source tree, and renaming their +extensions from `.go` to `.gno`. + +> As a small aid, this bash one-liner can be useful to convert all the file +> extensions: +> +> ```sh +> for i in *.go; do mv $i "$(echo $i | sed 's/\.go$/.gno/')"; done +> ``` + +Following that, the suggested approach is to iteratively try running `gno test .`, +while fixing any errors that may come out of trying to test the package. + +Some things to keep in mind: + +- Gno doesn't support assembly functions and build tags. Some Go packages may + contain assembly versions for different architecture and a `generic.go` file + containing the architecture-independent version. The general approach is that + of removing everything architecture/os-specific except for the `generic.go` file. +- Gno doesn't support reflection at the time of writing, which means that for + now many packages which rely heavily on reflection have to be delayed or + reduced while we figure out the details on how to implement reflection. + Aside from the `reflect` package itself, this also translates to very common + packages still not available in Gno, such as `fmt` or `encoding/json`. +- In the package documentation, specify the Go version from which the library + was taken. +- All changes from the Go standard libraries must be explicitly marked, possibly + with `// XXX` comments as needed. + +If you intend to create a PR to add a new standard library, remember to update +[Go\<\>Gno compatibility](go-gno-compatibility.md) accordingly. + +## Native bindings + +Gno has support for "natively-defined functions" exclusively within the standard +libraries. These are functions which are _declared_ in Gno code, but only _defined_ +in Go. There are generally three reasons why a function should be natively +defined: + +1. It relies on inspecting the Gno Virtual Machine itself.\ + For example: `std.AssertOriginCall`, `std.CurrentRealmPath`. +2. It relies on `unsafe`, or other features which are not planned to be + available in the GnoVM.\ + For example: `math.Float64frombits`. +3. Its native Go performance significantly outperforms the Gno counterpart by + several orders of magnitude, and it is used in crucial code or hot paths in + many programs.\ + For example: `sha256.Sum256`. + +The takeaway here is that native bindings are a special feature which can be +useful to overcome pure Gno limitations, but it is not a substitute for writing +standard libraries in Gno. + +There are three components to a natively bound function in Gno: + +1. The Gno function declaration, which must be a top-level function with no body + (and no brackets).\ + For example: `crypto/sha256/sha256.gno`. +2. The Go function definition, which must be a top-level function with the same + name and signature.\ + For example: `crypto/sha256/sha256.go`. +3. When the two above are present and valid, the native binding can be created + by executing the code generator: either execute `go generate` from the + `stdlibs` directory, or run `make generate` from the `gnovm` directory.\ + This generates the `native.go` file available in the `stdlibs` directory, + which provides the binding itself to then be used by the GnoVM. + +The code generator in question is available in the `misc/genstd` directory. +There are some quirks and features that must be kept in mind when writing native +bindings, which are the following: + +- Unexported functions (for instance, `func sum256(b []byte)`) must have their + Go counterpart prefixed with `X_` in order to make the functions exported (ie. + `func X_sum256(b []byte)`). +- The Go function declaration may specify as the first argument + `m *gno.Machine`, where `gno` is an import for + `github.com/gnolang/gno/gnovm/pkg/gnolang`. This gives the function access to + the Virtual Machine state, and is used by functions like `std.AssertOriginCall()`. +- The Go function may change the type of any parameter or result to + `gno.TypedValue` (where `gno` is an import for the above import path). This + means that the `native.go` generated code will not attempt to automatically + convert the Gno value into the Go value, and can be useful for unsupported + conversions like interface values. +- A small set of named types are "linked" between their Gno version and Go + counterpart. For instance, `std.Address` in Gno is + `(".../tm2/pkg/crypto").Bech32Address` in Go. A list of these can be found in + `misc/genstd/mapping.go`. +- Not all type literals are currently supported when converting from their Gno + version to their Go counterpart. Notable omissions at the time of writing + include struct and map literals. If you intend to use these, modify the code + generator to support them. +- The code generator does not inspect any imported packages from the Go native code + to determine the default package identifier (ie. the `package` clause). + Ie. if a package is in `foo/bar`, but declares `package xyz`, when importing + foo/bar the generator will assume the name to be `bar` instead of `xyz`. + You can add an identifier to the import to fix this and use the identifier + you want/need, ie.: `import gno "github.com/gnolang/gno/gnovm/pkg/gnolang"`. -* 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/examples/gno.land/p/demo/avl/z_0_filetest.gno b/examples/gno.land/p/demo/avl/z_0_filetest.gno index 814e19d6d49..e91788ac8eb 100644 --- a/examples/gno.land/p/demo/avl/z_0_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_0_filetest.gno @@ -267,6 +267,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -301,6 +303,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/examples/gno.land/p/demo/avl/z_1_filetest.gno b/examples/gno.land/p/demo/avl/z_1_filetest.gno index 410e9e93601..cdd56a5ad89 100644 --- a/examples/gno.land/p/demo/avl/z_1_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_1_filetest.gno @@ -291,6 +291,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -325,6 +327,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno index b707527c6a4..bec7338db42 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno @@ -69,6 +69,25 @@ func (s *basicNFT) TokenURI(tid TokenID) (string, error) { return uri.(string), nil } +func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { + // check for invalid TokenID + if !s.exists(tid) { + return false, ErrInvalidTokenId + } + + // check for the right owner + owner, err := s.OwnerOf(tid) + if err != nil { + return false, err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return false, ErrCallerIsNotOwner + } + s.tokenURIs.Set(string(tid), string(tURI)) + return true, nil +} + // IsApprovedForAll returns true if operator is approved for all by the owner. // Otherwise, returns false func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool { diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno index 7ad378af2f7..925f1fca44b 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno @@ -4,6 +4,7 @@ import ( "std" "testing" + "gno.land/p/demo/testutils" "gno.land/r/demo/users" ) @@ -367,3 +368,47 @@ func TestBurn(t *testing.T) { t.Errorf("should result in error") } } + +func TestSetTokenURI(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + if dummy == nil { + t.Errorf("should not be nil") + } + + addr1 := users.AddressOrName("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := users.AddressOrName("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + tokenURI := "http://example.com/token" + + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummy.mint(addr1.Resolve(), TokenID("1")) + _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) + + if derr != nil { + t.Errorf("Should not result in error ", derr.Error()) + } + + // Test case: Invalid token ID + _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) + if err != ErrInvalidTokenId { + t.Errorf("Expected error %v, got %v", ErrInvalidTokenId, err) + } + + std.TestSetOrigCaller(std.Address(addr2)) // addr2 + + _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 + if cerr != ErrCallerIsNotOwner { + t.Errorf("Expected error %v, got %v", ErrCallerIsNotOwner, err) + } + + // Test case: Retrieving TokenURI + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummyTokenURI, err := dummy.TokenURI(TokenID("1")) + if err != nil { + t.Errorf("TokenURI error: %v, ", err.Error()) + } + if dummyTokenURI != tokenURI { + t.Errorf("Expected URI %v, got %v", tokenURI, dummyTokenURI) + } +} diff --git a/examples/gno.land/p/demo/grc/grc721/gno.mod b/examples/gno.land/p/demo/grc/grc721/gno.mod index ea8c9c9e52e..952e0cb8ee4 100644 --- a/examples/gno.land/p/demo/grc/grc721/gno.mod +++ b/examples/gno.land/p/demo/grc/grc721/gno.mod @@ -2,6 +2,7 @@ module gno.land/p/demo/grc/grc721 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/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721.gno b/examples/gno.land/p/demo/grc/grc721/igrc721.gno index d60308e11a1..387547a7e26 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721.gno @@ -5,6 +5,7 @@ import "std" type IGRC721 interface { BalanceOf(owner std.Address) (uint64, error) OwnerOf(tid TokenID) (std.Address, error) + SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) SafeTransferFrom(from, to std.Address, tid TokenID) error TransferFrom(from, to std.Address, tid TokenID) error Approve(approved std.Address, tid TokenID) error @@ -13,7 +14,10 @@ type IGRC721 interface { IsApprovedForAll(owner, operator std.Address) bool } -type TokenID string +type ( + TokenID string + TokenURI string +) type TransferEvent struct { From std.Address diff --git a/examples/gno.land/p/demo/tamagotchi/gno.mod b/examples/gno.land/p/demo/tamagotchi/gno.mod new file mode 100644 index 00000000000..58441284a6b --- /dev/null +++ b/examples/gno.land/p/demo/tamagotchi/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/tamagotchi + +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/p/demo/tamagotchi/tamagotchi.gno b/examples/gno.land/p/demo/tamagotchi/tamagotchi.gno new file mode 100644 index 00000000000..4e0cb6cb2b2 --- /dev/null +++ b/examples/gno.land/p/demo/tamagotchi/tamagotchi.gno @@ -0,0 +1,175 @@ +package tamagotchi + +import ( + "time" + + "gno.land/p/demo/ufmt" +) + +// Tamagotchi structure +type Tamagotchi struct { + name string + hunger int + happiness int + health int + age int + maxAge int + sleepy int + created time.Time + lastUpdated time.Time +} + +func New(name string) *Tamagotchi { + now := time.Now() + return &Tamagotchi{ + name: name, + hunger: 50, + happiness: 50, + health: 50, + maxAge: 100, + lastUpdated: now, + created: now, + } +} + +func (t *Tamagotchi) Name() string { + t.update() + return t.name +} + +func (t *Tamagotchi) Hunger() int { + t.update() + return t.hunger +} + +func (t *Tamagotchi) Happiness() int { + t.update() + return t.happiness +} + +func (t *Tamagotchi) Health() int { + t.update() + return t.health +} + +func (t *Tamagotchi) Age() int { + t.update() + return t.age +} + +func (t *Tamagotchi) Sleepy() int { + t.update() + return t.sleepy +} + +// Feed method for Tamagotchi +func (t *Tamagotchi) Feed() { + t.update() + if t.dead() { + return + } + t.hunger = bound(t.hunger-10, 0, 100) +} + +// Play method for Tamagotchi +func (t *Tamagotchi) Play() { + t.update() + if t.dead() { + return + } + t.happiness = bound(t.happiness+10, 0, 100) +} + +// Heal method for Tamagotchi +func (t *Tamagotchi) Heal() { + t.update() + + if t.dead() { + return + } + t.health = bound(t.health+10, 0, 100) +} + +func (t Tamagotchi) dead() bool { return t.health == 0 } + +// Update applies changes based on the duration since the last update +func (t *Tamagotchi) update() { + if t.dead() { + return + } + + now := time.Now() + if t.lastUpdated == now { + return + } + + duration := now.Sub(t.lastUpdated) + elapsedMins := int(duration.Minutes()) + + t.hunger = bound(t.hunger+elapsedMins, 0, 100) + t.happiness = bound(t.happiness-elapsedMins, 0, 100) + t.health = bound(t.health-elapsedMins, 0, 100) + t.sleepy = bound(t.sleepy+elapsedMins, 0, 100) + + // age is hours since created + t.age = int(now.Sub(t.created).Hours()) + if t.age > t.maxAge { + t.age = t.maxAge + t.health = 0 + } + if t.health == 0 { + t.sleepy = 0 + t.happiness = 0 + t.hunger = 0 + } + + t.lastUpdated = now +} + +// Face returns an ASCII art representation of the Tamagotchi's current state +func (t *Tamagotchi) Face() string { + t.update() + return t.face() +} + +func (t *Tamagotchi) face() string { + switch { + case t.health == 0: + return "😵" // dead face + case t.health < 30: + return "😷" // sick face + case t.happiness < 30: + return "😢" // sad face + case t.hunger > 70: + return "😫" // hungry face + case t.sleepy > 70: + return "😴" // sleepy face + default: + return "😃" // happy face + } +} + +// Markdown method for Tamagotchi +func (t *Tamagotchi) Markdown() string { + t.update() + return ufmt.Sprintf(`# %s %s + +* age: %d +* hunger: %d +* happiness: %d +* health: %d +* sleepy: %d`, + t.name, t.Face(), + t.age, t.hunger, t.happiness, t.health, t.sleepy, + ) +} + +func bound(n, min, max int) int { + if n < min { + return min + } + if n > max { + return max + } + return n +} diff --git a/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno b/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno new file mode 100644 index 00000000000..36163065e7f --- /dev/null +++ b/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno @@ -0,0 +1,106 @@ +package main + +import ( + "std" + "time" + + "internal/os_test" + + "gno.land/p/demo/tamagotchi" +) + +func main() { + t := tamagotchi.New("Gnome") + + println("\n-- INITIAL\n") + println(t.Markdown()) + + println("\n-- WAIT 20 minutes\n") + os_test.Sleep(20 * time.Minute) + println(t.Markdown()) + + println("\n-- FEEDx3, PLAYx2, HEALx4\n") + t.Feed() + t.Feed() + t.Feed() + t.Play() + t.Play() + t.Heal() + t.Heal() + t.Heal() + t.Heal() + println(t.Markdown()) + + println("\n-- WAIT 20 minutes\n") + os_test.Sleep(20 * time.Minute) + println(t.Markdown()) + + println("\n-- WAIT 20 hours\n") + os_test.Sleep(20 * time.Hour) + println(t.Markdown()) + + println("\n-- WAIT 20 hours\n") + os_test.Sleep(20 * time.Hour) + println(t.Markdown()) +} + +// Output: +// -- INITIAL +// +// # Gnome 😃 +// +// * age: 0 +// * hunger: 50 +// * happiness: 50 +// * health: 50 +// * sleepy: 0 +// +// -- WAIT 20 minutes +// +// # Gnome 😃 +// +// * age: 0 +// * hunger: 70 +// * happiness: 30 +// * health: 30 +// * sleepy: 20 +// +// -- FEEDx3, PLAYx2, HEALx4 +// +// # Gnome 😃 +// +// * age: 0 +// * hunger: 40 +// * happiness: 50 +// * health: 70 +// * sleepy: 20 +// +// -- WAIT 20 minutes +// +// # Gnome 😃 +// +// * age: 0 +// * hunger: 60 +// * happiness: 30 +// * health: 50 +// * sleepy: 40 +// +// -- WAIT 20 hours +// +// # Gnome 😵 +// +// * age: 20 +// * hunger: 0 +// * happiness: 0 +// * health: 0 +// * sleepy: 0 +// +// -- WAIT 20 hours +// +// # Gnome 😵 +// +// * age: 20 +// * hunger: 0 +// * happiness: 0 +// * health: 0 +// * sleepy: 0 diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno index 749566ea5bc..c891a352d8c 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -374,7 +374,7 @@ func main() { // "Escaped": true, // "ObjectID": "336074805fc853987abe6f7fe3ad97a6a6f3077a:2" // }, -// "Index": "188", +// "Index": "189", // "TV": null // } // } @@ -541,7 +541,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "8164abed5231309c88497013f7da72a1b5d427b0", +// "Hash": "25ffc45509708ca0ae17271cb4c3a1dfb367b965", // "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:115" // } // }, @@ -847,7 +847,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "5b4b593f1d4b37cb99166247ea28174f91087fdd", +// "Hash": "a8e67b9881af89ca2ec2f05778bf7528a54a5833", // "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:82" // } // }, @@ -865,7 +865,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "7e9fd9bb5e90a06c7751585cd80f23aedddde25b", +// "Hash": "d8ae14a4620e3c6dedabd76cd0c5d7e3c205d647", // "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:83" // } // }, diff --git a/examples/gno.land/r/demo/tamagotchi/gno.mod b/examples/gno.land/r/demo/tamagotchi/gno.mod new file mode 100644 index 00000000000..b7a2deea2c2 --- /dev/null +++ b/examples/gno.land/r/demo/tamagotchi/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/tamagotchi + +require ( + gno.land/p/demo/tamagotchi v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/tamagotchi/realm.gno b/examples/gno.land/r/demo/tamagotchi/realm.gno new file mode 100644 index 00000000000..f8f62c9fc7a --- /dev/null +++ b/examples/gno.land/r/demo/tamagotchi/realm.gno @@ -0,0 +1,53 @@ +package tamagotchi + +import ( + "std" + + "gno.land/p/demo/tamagotchi" + "gno.land/p/demo/ufmt" +) + +var t *tamagotchi.Tamagotchi + +func init() { + Reset("gnome#0") +} + +func Reset(optionalName string) string { + name := optionalName + if name == "" { + height := std.GetHeight() + name = ufmt.Sprintf("gnome#%d", height) + } + + t = tamagotchi.New(name) + + return ufmt.Sprintf("A new tamagotchi is born. Their name is %s %s.", t.Name(), t.Face()) +} + +func Feed() string { + t.Feed() + return t.Markdown() +} + +func Play() string { + t.Play() + return t.Markdown() +} + +func Heal() string { + t.Heal() + return t.Markdown() +} + +func Render(path string) string { + tama := t.Markdown() + links := `Actions: +* [Feed](/r/demo/tamagotchi?help&__func=Feed) +* [Play](/r/demo/tamagotchi?help&__func=Play) +* [Heal](/r/demo/tamagotchi?help&__func=Heal) +* [Reset](/r/demo/tamagotchi?help&__func=Reset) +` + + return tama + "\n\n" + links +} diff --git a/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno b/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno new file mode 100644 index 00000000000..373d737b3b5 --- /dev/null +++ b/examples/gno.land/r/demo/tamagotchi/z0_filetest.gno @@ -0,0 +1,28 @@ +package main + +import ( + "std" + "time" + + "gno.land/r/demo/tamagotchi" +) + +func main() { + tamagotchi.Reset("tamagnotchi") + println(tamagotchi.Render("")) +} + +// Output: +// # tamagnotchi 😃 +// +// * age: 0 +// * hunger: 50 +// * happiness: 50 +// * health: 50 +// * sleepy: 0 +// +// Actions: +// * [Feed](/r/demo/tamagotchi?help&__func=Feed) +// * [Play](/r/demo/tamagotchi?help&__func=Play) +// * [Heal](/r/demo/tamagotchi?help&__func=Heal) +// * [Reset](/r/demo/tamagotchi?help&__func=Reset) diff --git a/examples/gno.land/r/demo/wugnot/gno.mod b/examples/gno.land/r/demo/wugnot/gno.mod new file mode 100644 index 00000000000..1f03ded515c --- /dev/null +++ b/examples/gno.land/r/demo/wugnot/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/wugnot + +require ( + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/wugnot/wugnot.gno b/examples/gno.land/r/demo/wugnot/wugnot.gno new file mode 100644 index 00000000000..82c3c43db89 --- /dev/null +++ b/examples/gno.land/r/demo/wugnot/wugnot.gno @@ -0,0 +1,126 @@ +package wugnot + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" +) + +var ( + // wugnot is the admin token, able to mint and burn. + wugnot *grc20.AdminToken = grc20.NewAdminToken("wrapped GNOT", "wugnot", 0) + // WUGNOT is the banker usable by users directly. + WUGNOT = wugnot.GRC20() +) + +const ( + ugnotMinDeposit uint64 = 1000 + wugnotMinDeposit uint64 = 1 +) + +// wrapper. +// + +func Deposit() { + caller := std.PrevRealm().Addr() + sent := std.GetOrigSend() + amount := sent.AmountOf("ugnot") + + if uint64(amount) < ugnotMinDeposit { + panic(ufmt.Sprintf("Deposit below minimum: %d/%d ugnot.", amount, ugnotMinDeposit)) + } + wugnot.Mint(caller, uint64(amount)) +} + +func Withdraw(amount uint64) { + if amount < wugnotMinDeposit { + panic(ufmt.Sprintf("Deposit below minimum: %d/%d wugnot.", amount, wugnotMinDeposit)) + } + + caller := std.PrevRealm().Addr() + pkgaddr := std.GetOrigPkgAddr() + + callerBal, _ := wugnot.BalanceOf(caller) + if callerBal < amount { + panic(ufmt.Sprintf("Insufficient balance: %d available, %d needed.", callerBal, amount)) + } + + // send swapped ugnots to caller + banker := std.GetBanker(std.BankerTypeRealmSend) + send := std.Coins{{"ugnot", int64(amount)}} + banker.SendCoins(pkgaddr, caller, send) + wugnot.Burn(caller, amount) +} + +// render. +// + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return wugnot.RenderHome() + case c == 2 && parts[0] == "balance": + owner := std.Address(parts[1]) + balance, _ := wugnot.BalanceOf(owner) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +// XXX: if we could call WUGNOT.XXX instead of XXX from gnokey, then, all the following lines would not be needed. + +// direct getters. +// XXX: remove them in favor of q_call wugnot.XXX + +func TotalSupply() uint64 { + return wugnot.TotalSupply() +} + +func BalanceOf(owner std.Address) uint64 { + balance, err := wugnot.BalanceOf(owner) + if err != nil { + panic(err) + } + return balance +} + +func Allowance(owner, spender std.Address) uint64 { + allowance, err := wugnot.Allowance(owner, spender) + if err != nil { + panic(err) + } + return allowance +} + +// setters. +// + +func Transfer(to std.Address, amount uint64) { + caller := std.PrevRealm().Addr() + err := wugnot.Transfer(caller, to, amount) + if err != nil { + panic(err) + } +} + +func Approve(spender std.Address, amount uint64) { + caller := std.PrevRealm().Addr() + err := wugnot.Approve(caller, spender, amount) + if err != nil { + panic(err) + } +} + +func TransferFrom(from, to std.Address, amount uint64) { + caller := std.PrevRealm().Addr() + err := wugnot.TransferFrom(caller, from, to, amount) + if err != nil { + panic(err) + } +} diff --git a/examples/gno.land/r/demo/wugnot/z0_filetest.gno b/examples/gno.land/r/demo/wugnot/z0_filetest.gno new file mode 100644 index 00000000000..fa2f45682b1 --- /dev/null +++ b/examples/gno.land/r/demo/wugnot/z0_filetest.gno @@ -0,0 +1,75 @@ +// PKGPATH: gno.land/r/demo/wugnot_test +package wugnot_test + +import ( + "fmt" + "std" + + "gno.land/p/demo/testutils" + "gno.land/r/demo/wugnot" +) + +var ( + addr1 = testutils.TestAddress("test1") + addrc = std.DerivePkgAddr("gno.land/r/demo/wugnot") + addrt = std.DerivePkgAddr("gno.land/r/demo/wugnot_test") +) + +func main() { + std.TestSetOrigPkgAddr(addrc) + std.TestIssueCoins(addrc, std.Coins{{"ugnot", 100000001}}) // TODO: remove this + + // issue ugnots + std.TestIssueCoins(addr1, std.Coins{{"ugnot", 100000001}}) + + // print initial state + printBalances() + // println(wugnot.Render("queues")) + // println("A -", wugnot.Render("")) + + std.TestSetOrigCaller(addr1) + std.TestSetOrigSend(std.Coins{{"ugnot", 123_400}}, nil) + wugnot.Deposit() + printBalances() + wugnot.Withdraw(4242) + printBalances() +} + +func printBalances() { + printSingleBalance := func(name string, addr std.Address) { + wugnotBal := wugnot.BalanceOf(addr) + std.TestSetOrigCaller(addr) + abanker := std.GetBanker(std.BankerTypeOrigSend) + acoins := abanker.GetCoins(addr).AmountOf("ugnot") + bbanker := std.GetBanker(std.BankerTypeRealmIssue) + bcoins := bbanker.GetCoins(addr).AmountOf("ugnot") + cbanker := std.GetBanker(std.BankerTypeRealmSend) + ccoins := cbanker.GetCoins(addr).AmountOf("ugnot") + dbanker := std.GetBanker(std.BankerTypeReadonly) + dcoins := dbanker.GetCoins(addr).AmountOf("ugnot") + fmt.Printf("| %-13s | addr=%s | wugnot=%-5d | ugnot=%-9d %-9d %-9d %-9d |\n", + name, addr, wugnotBal, acoins, bcoins, ccoins, dcoins) + } + println("-----------") + printSingleBalance("wugnot_test", addrt) + printSingleBalance("wugnot", addrc) + printSingleBalance("addr1", addr1) + println("-----------") +} + +// Output: +// ----------- +// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=0 | ugnot=200000000 200000000 200000000 200000000 | +// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=100000001 100000001 100000001 100000001 | +// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 100000001 100000001 100000001 | +// ----------- +// ----------- +// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=123400 | ugnot=200000000 200000000 200000000 200000000 | +// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=100000001 100000001 100000001 100000001 | +// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 100000001 100000001 100000001 | +// ----------- +// ----------- +// | wugnot_test | addr=g19rmydykafrqyyegc8uuaxxpzqwzcnxraj2dev9 | wugnot=119158 | ugnot=200004242 200004242 200004242 200004242 | +// | wugnot | addr=g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 | wugnot=0 | ugnot=99995759 99995759 99995759 99995759 | +// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | wugnot=0 | ugnot=100000001 100000001 100000001 100000001 | +// ----------- diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno index 5f2a5b9c4b5..14da10dd63a 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -106,17 +106,8 @@ func worxDAO() ui.Element { ## Contributors ``*/ return ui.Element{ - ui.H3("WorxDAO (WIP)"), - ui.Text(`- A - - A1 - - A1A - - A1B - - A2 - - A3 - - A3A - - A3A1 -- B -- C`), + ui.H3("Contributions (WorxDAO & GoR)"), + ui.Text(`TODO: GoR dashboard + WorxDAO topics`), } } diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno index 1fffc11792f..e87e5917676 100644 --- a/examples/gno.land/r/gnoland/home/home_filetest.gno +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -19,18 +19,9 @@ func main() { // //
// -// ### WorxDAO (WIP) -// -// - A -// - A1 -// - A1A -// - A1B -// - A2 -// - A3 -// - A3A -// - A3A1 -// - B -// - C +// ### Contributions (WorxDAO & GoR) +// +// TODO: GoR dashboard + WorxDAO topics //
// // ### Explore New Packages and Realms diff --git a/gno.land/Makefile b/gno.land/Makefile index 1297da393cb..982063d155c 100644 --- a/gno.land/Makefile +++ b/gno.land/Makefile @@ -5,8 +5,16 @@ help: rundep=go run -modfile ../misc/devdeps/go.mod -.PHONY: gnoland.start -gnoland.start:; go run ./cmd/gnoland start +gnoland.start: + @#TODO: remove this makefile directive in a few weeks. + @echo "DEPRECATED: use 'start.gnoland' instead of 'gnoland.start'" + go run ./cmd/gnoland start + +.PHONY: start.gnoland +start.gnoland:; go run ./cmd/gnoland start + +.PHONY: start.gnoweb +start.gnoweb:; go run ./cmd/gnoweb .PHONY: build build: build.gnoland build.gnokey build.gnoweb build.gnofaucet build.gnotxsync build.genesis diff --git a/gno.land/cmd/gnofaucet/serve.go b/gno.land/cmd/gnofaucet/serve.go index e00406a6e80..9d579e90fe3 100644 --- a/gno.land/cmd/gnofaucet/serve.go +++ b/gno.land/cmd/gnofaucet/serve.go @@ -337,7 +337,9 @@ func execServe(cfg *config, args []string, io commands.IO) error { Addr: ":5050", ReadHeaderTimeout: 60 * time.Second, } - server.ListenAndServe() + if err := server.ListenAndServe(); err != nil { + return fmt.Errorf("http server stopped. %w", err) + } return nil } diff --git a/gno.land/cmd/gnoland/testdata/append.txtar b/gno.land/cmd/gnoland/testdata/append.txtar new file mode 100644 index 00000000000..792d71882e5 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/append.txtar @@ -0,0 +1,129 @@ +# start a new node +gnoland start + +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/append -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call Append 1 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '1' -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func AppendNil -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call Append 2 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '2' -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call Append 3 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '3' -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("1-2-3-" string)' +stdout OK! + +# Call Pop +gnokey maketx call -pkgpath gno.land/r/append -func Pop -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("2-3-" string)' +stdout OK! + +# Call Append 42 +gnokey maketx call -pkgpath gno.land/r/append -func Append -gas-fee 1000000ugnot -gas-wanted 2000000 -args '42' -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("2-3-42-" string)' +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func CopyAppend -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func PopB -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# Call render +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("2-3-42-" string)' +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func AppendMoreAndC -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func ReassignC -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '("2-3-42-70-100-" string)' +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/append -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args 'd' -broadcast -chainid=tendermint_test test1 +stdout '("1-" string)' +stdout OK! + +-- append.gno -- +package append + +import ( + "gno.land/p/demo/ufmt" +) + +type T struct{ i int } + +var a, b, d []T +var c = []T{{i: 100}} + + +func init() { + a = make([]T, 0, 1) +} + +func Pop() { + a = append(a[:0], a[1:]...) +} + +func Append(i int) { + a = append(a, T{i: i}) +} + +func CopyAppend() { + b = append(a, T{i: 50}, T{i: 60}) +} + +func PopB() { + b = append(b[:0], b[1:]...) +} + +func AppendMoreAndC() { + // Fill to capacity + a = append(a, T{i: 70}) + // Above capacity; make new array + a = append(a, c...) +} + +func ReassignC() { + c[0] = T{i: 200} +} + +func AppendNil() { + d = append(d, a...) +} + +func Render(path string) string { + source := a + if path == "d" { + source = d + } + + var s string + for i:=0;i *Game + +var counter byte + +func New(s string) string { + // Bug shows if Moves has a cap > 0 when initialised. + el := &Game{Position: Position{Moves: make([]Move, 0, 2)}} + games.Set(s, el) + return values(el.Position) +} + +func Delta(s string) string { + v, _ := games.Get(s) + g, ok := v.(*Game) + if !ok { + panic("invalid game") + } + n := g.Position.update() + g.Position = n + ret := values(n) + return ret +} + +func Render(s string) string { + v, _ := games.Get(s) + g, ok := v.(*Game) + if !ok { + panic("invalid game") + } + return values(g.Position) +} + +func values(x Position) string { + s := "" + for _, val := range x.Moves { + s += strconv.Itoa(int(val.N1)) + "," + strconv.Itoa(int(val.N2)) + "," + strconv.Itoa(int(val.N3)) + ";" + } + return s +} diff --git a/gno.land/cmd/gnoland/testdata/wugnot.txtar b/gno.land/cmd/gnoland/testdata/wugnot.txtar new file mode 100644 index 00000000000..5c2d7d3cb90 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/wugnot.txtar @@ -0,0 +1,43 @@ +gnoland start + +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout '# wrapped GNOT \(\$wugnot\)' +stdout 'Decimals..: 0' +stdout 'Total supply..: 0' +stdout 'Known accounts..: 0' +stdout 'OK!' + +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -send 12345678ugnot -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout 'OK!' + +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout 'Total supply..: 12345678' +stdout 'Known accounts..: 1' +stdout 'OK!' + +# XXX: use test2 instead (depends on https://github.com/gnolang/gno/issues/1269#issuecomment-1806386069) +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -send 12345678ugnot -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout 'OK!' + +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout 'Total supply..: 24691356' +stdout 'Known accounts..: 1' # should be 2 once we can use test2 +stdout 'OK!' + +# XXX: replace hardcoded address with test3 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Transfer -gas-fee 1000000ugnot -gas-wanted 2000000 -args 'g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq' -args '10000000' -broadcast -chainid=tendermint_test test1 +stdout 'OK!' + +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout 'Total supply..: 24691356' +stdout 'Known accounts..: 2' # should be 3 once we can use test2 +stdout 'OK!' + +# XXX: use test3 instead (depends on https://github.com/gnolang/gno/issues/1269#issuecomment-1806386069) +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Withdraw -args 10000000 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout 'OK!' + +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +stdout 'Total supply..: 14691356' +stdout 'Known accounts..: 2' # should be 3 once we can use test2 +stdout 'OK!' diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index b080e0b403d..34b12928367 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -1,505 +1,58 @@ -// main.go - package main import ( - "encoding/json" - "errors" "flag" "fmt" - "io" "net/http" "os" - "path/filepath" - "runtime" - "strings" "time" - "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" - "github.com/gnolang/gno/tm2/pkg/std" - "github.com/gorilla/mux" - "github.com/gotuna/gotuna" - - "github.com/gnolang/gno/gno.land/cmd/gnoweb/static" // for static files - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types + // for static files + "github.com/gnolang/gno/gno.land/pkg/gnoweb" + "github.com/gnolang/gno/tm2/pkg/log" + // for error types // "github.com/gnolang/gno/tm2/pkg/sdk" // for baseapp (info, status) ) -const ( - qFileStr = "vm/qfile" -) - -var startedAt time.Time - -var flags struct { - BindAddr string - RemoteAddr string - CaptchaSite string - FaucetURL string - ViewsDir string - HelpChainID string - HelpRemote string - WithAnalytics bool -} - -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") // 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), - Router: gotuna.NewMuxRouter(), - Static: static.EmbeddedStatic, - // StaticPrefix: "static/", - } - - // 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) - server := &http.Server{ - Addr: flags.BindAddr, - ReadHeaderTimeout: 60 * time.Second, - Handler: makeApp().Router, - } - - if err := server.ListenAndServe(); err != nil { - fmt.Fprintf(os.Stderr, "HTTP server stopped with error: %+v\n", err) - } -} - -// 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) { - 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 - } - - 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, - }) - } - - 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("Flags", flags). - Render(w, r, "faucet.html", "funcs.html") - }) -} - -func handlerStatusJSON(app gotuna.App) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ret struct { - Gnoland struct { - Connected bool `json:"connected"` - Error *string `json:"error,omitempty"` - Height *int64 `json:"height,omitempty"` - // processed txs - // active connections - - Version *string `json:"version,omitempty"` - // Uptime *float64 `json:"uptime-seconds,omitempty"` - // Goarch *string `json:"goarch,omitempty"` - // Goos *string `json:"goos,omitempty"` - // GoVersion *string `json:"go-version,omitempty"` - // NumCPU *int `json:"num_cpu,omitempty"` - } `json:"gnoland"` - Website struct { - // Version string `json:"version"` - Uptime float64 `json:"uptime-seconds"` - Goarch string `json:"goarch"` - Goos string `json:"goos"` - GoVersion string `json:"go-version"` - NumCPU int `json:"num_cpu"` - } `json:"website"` - } - ret.Website.Uptime = time.Since(startedAt).Seconds() - ret.Website.Goarch = runtime.GOARCH - ret.Website.Goos = runtime.GOOS - ret.Website.NumCPU = runtime.NumCPU() - ret.Website.GoVersion = runtime.Version() - - ret.Gnoland.Connected = true - res, err := makeRequest(".app/version", []byte{}) - if err != nil { - ret.Gnoland.Connected = false - errmsg := err.Error() - ret.Gnoland.Error = &errmsg - } else { - version := string(res.Value) - ret.Gnoland.Version = &version - ret.Gnoland.Height = &res.Height - } - - out, _ := json.MarshalIndent(ret, "", " ") - w.Header().Set("Content-Type", "application/json") - w.Write(out) - }) -} - -func handlerRedirect(app gotuna.App, to string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - 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") - }) -} - -func handlerRealmMain(app gotuna.App) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - rlmname := vars["rlmname"] - rlmpath := "gno.land/r/" + rlmname - query := r.URL.Query() - if query.Has("help") { - // Render function helper. - funcName := query.Get("__func") - qpath := "vm/qfuncs" - data := []byte(rlmpath) - res, err := makeRequest(qpath, data) - if err != nil { - writeError(w, err) - return - } - var fsigs vm.FunctionSignatures - amino.MustUnmarshalJSON(res.Data, &fsigs) - // Fill fsigs with query parameters. - for i := range fsigs { - fsig := &(fsigs[i]) - for j := range fsig.Params { - param := &(fsig.Params[j]) - value := query.Get(param.Name) - param.Value = value - } - } - // Render template. - tmpl := app.NewTemplatingEngine() - tmpl.Set("FuncName", funcName) - tmpl.Set("RealmPath", rlmpath) - 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. - qpath := qFileStr - data := []byte(rlmpath) - _, err := makeRequest(qpath, data) - if err != nil { - writeError(w, errors.New("error querying realm package")) - return - } - // Render blank query path, /r/REALM:. - handleRealmRender(app, w, r) - } - }) -} - -type pathLink struct { - URL string - Text string -} - -func handlerRealmRender(app gotuna.App) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handleRealmRender(app, w, r) - }) -} - -func handleRealmRender(app gotuna.App, w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - rlmname := vars["rlmname"] - rlmpath := "gno.land/r/" + rlmname - querystr := vars["querystr"] - if r.URL.Path == "/r/"+rlmname+":" { - // Redirect to /r/REALM if querypath is empty. - http.Redirect(w, r, "/r/"+rlmname, http.StatusFound) - return - } - qpath := "vm/qrender" - data := []byte(fmt.Sprintf("%s\n%s", rlmpath, querystr)) - res, err := makeRequest(qpath, data) + err := runMain(os.Args[1:]) if err != nil { - // XXX hack - if strings.Contains(err.Error(), "Render not declared") { - res = &abci.ResponseQuery{} - res.Data = []byte("realm package has no Render() function") - } else { - writeError(w, err) - return - } - } - // linkify querystr. - 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, - }) + _, _ = fmt.Fprintf(os.Stderr, "%+v\n", err) + os.Exit(1) } - // 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") } -func handlerRealmFile(app gotuna.App) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - diruri := "gno.land/r/" + vars["rlmname"] - filename := vars["filename"] - renderPackageFile(app, w, r, diruri, filename) - }) -} - -func handlerPackageFile(app gotuna.App) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - pkgpath := "gno.land/p/" + vars["filepath"] - diruri, filename := std.SplitFilepath(pkgpath) - if filename == "" && diruri == pkgpath { - // redirect to diruri + "/" - http.Redirect(w, r, "/p/"+vars["filepath"]+"/", http.StatusFound) - return - } - renderPackageFile(app, w, r, diruri, filename) - }) -} +func runMain(args []string) error { + var ( + fs = flag.NewFlagSet("gnoweb", flag.ContinueOnError) + cfg = gnoweb.NewDefaultConfig() + bindAddress string + ) + fs.StringVar(&cfg.RemoteAddr, "remote", cfg.RemoteAddr, "remote gnoland node address") + fs.StringVar(&cfg.CaptchaSite, "captcha-site", cfg.CaptchaSite, "recaptcha site key (if empty, captcha are disabled)") + fs.StringVar(&cfg.FaucetURL, "faucet-url", cfg.FaucetURL, "faucet server URL") + fs.StringVar(&cfg.ViewsDir, "views-dir", cfg.ViewsDir, "views directory location") // XXX: replace with goembed + fs.StringVar(&cfg.HelpChainID, "help-chainid", cfg.HelpChainID, "help page's chainid") + fs.StringVar(&cfg.HelpRemote, "help-remote", cfg.HelpRemote, "help page's remote addr") + fs.BoolVar(&cfg.WithAnalytics, "with-analytics", cfg.WithAnalytics, "enable privacy-first analytics") + fs.StringVar(&bindAddress, "bind", "127.0.0.1:8888", "server listening address") -func renderPackageFile(app gotuna.App, w http.ResponseWriter, r *http.Request, diruri string, filename string) { - if filename == "" { - // Request is for a folder. - qpath := qFileStr - data := []byte(diruri) - res, err := makeRequest(qpath, data) - if err != nil { - writeError(w, err) - return - } - files := strings.Split(string(res.Data), "\n") - // Render template. - tmpl := app.NewTemplatingEngine() - 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. - filepath := diruri + "/" + filename - qpath := qFileStr - data := []byte(filepath) - res, err := makeRequest(qpath, data) - if err != nil { - writeError(w, err) - return - } - // Render template. - tmpl := app.NewTemplatingEngine() - tmpl.Set("DirURI", diruri) - 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") + if err := fs.Parse(args); err != nil { + return err } -} -func makeRequest(qpath string, data []byte) (res *abci.ResponseQuery, err error) { - opts2 := client.ABCIQueryOptions{ - // Height: height, XXX - // Prove: false, XXX - } - remote := flags.RemoteAddr - cli := client.NewHTTP(remote, "/websocket") - qres, err := cli.ABCIQueryWithOptions( - qpath, data, opts2) - if err != nil { - return nil, err - } - if qres.Response.Error != nil { - fmt.Printf("Log: %s\n", - qres.Response.Log) - return nil, qres.Response.Error - } - return &qres.Response, nil -} - -func handlerStaticFile(app gotuna.App) http.Handler { - fs := http.FS(app.Static) - fileapp := http.StripPrefix("/static", http.FileServer(fs)) - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - fpath := filepath.Clean(vars["path"]) - f, err := fs.Open(fpath) - if os.IsNotExist(err) { - handleNotFound(app, fpath, w, r) - return - } - stat, err := f.Stat() - if err != nil || stat.IsDir() { - handleNotFound(app, fpath, w, r) - return - } - - // TODO: ModTime doesn't work for embed? - // w.Header().Set("ETag", fmt.Sprintf("%x", stat.ModTime().UnixNano())) - // w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%s", "31536000")) - fileapp.ServeHTTP(w, r) - }) -} - -func handlerFavicon(app gotuna.App) http.Handler { - fs := http.FS(app.Static) - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fpath := "img/favicon.ico" - f, err := fs.Open(fpath) - if os.IsNotExist(err) { - handleNotFound(app, fpath, w, r) - return - } - w.Header().Set("Content-Type", "image/x-icon") - w.Header().Set("Cache-Control", "public, max-age=604800") // 7d - io.Copy(w, f) - }) -} + logger := log.NewTMLogger(os.Stdout) + logger.SetLevel(log.LevelDebug) -func handleNotFound(app gotuna.App, path string, w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - 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()) + logger.Info("Running", "listener", "http://"+bindAddress) + server := &http.Server{ + Addr: bindAddress, + ReadHeaderTimeout: 60 * time.Second, + Handler: gnoweb.MakeApp(logger, cfg).Router, } - w.Write([]byte(err.Error())) -} - -func pathOf(diruri string) string { - parts := strings.Split(diruri, "/") - if parts[0] == "gno.land" { - return "/" + strings.Join(parts[1:], "/") - } else { - panic(fmt.Sprintf("invalid dir-URI %q", diruri)) + if err := server.ListenAndServe(); err != nil { + logger.Error("HTTP server stopped", " error:", err) } + return nil } diff --git a/gno.land/cmd/gnoweb/main_test.go b/gno.land/cmd/gnoweb/main_test.go index 2bec0a9ac37..640c4763140 100644 --- a/gno.land/cmd/gnoweb/main_test.go +++ b/gno.land/cmd/gnoweb/main_test.go @@ -1,126 +1,14 @@ package main import ( - "fmt" - "net/http" - "net/http/httptest" - "strings" + "errors" + "flag" "testing" - - "github.com/gnolang/gno/gno.land/pkg/integration" - "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/tm2/pkg/log" - "github.com/gotuna/gotuna/test/assert" ) -func TestRoutes(t *testing.T) { - const ( - ok = http.StatusOK - found = http.StatusFound - notFound = http.StatusNotFound - ) - routes := []struct { - route string - status int - substring string - }{ - {"/", ok, "Welcome"}, // assert / gives 200 (OK). assert / contains "Welcome". - {"/about", ok, "blockchain"}, - {"/r/gnoland/blog", ok, ""}, // whatever content - {"/r/gnoland/blog?help", ok, "exposed"}, - {"/r/gnoland/blog/", ok, "admin.gno"}, - {"/r/gnoland/blog/admin.gno", ok, "func "}, - {"/r/demo/users:administrator", ok, "address"}, - {"/r/demo/users", ok, "manfred"}, - {"/r/demo/users/types.gno", ok, "type "}, - {"/r/demo/deep/very/deep", ok, "it works!"}, - {"/r/demo/deep/very/deep:bob", ok, "hi bob"}, - {"/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"}, +func TestFlagHelp(t *testing.T) { + err := runMain([]string{"-h"}) + if !errors.Is(err, flag.ErrHelp) { + t.Errorf("should display usage") } - - config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) - 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 { - t.Run(fmt.Sprintf("test route %s", r.route), func(t *testing.T) { - request := httptest.NewRequest(http.MethodGet, r.route, nil) - response := httptest.NewRecorder() - app.Router.ServeHTTP(response, request) - assert.Equal(t, r.status, response.Code) - 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, gnoenv.RootDir()) - 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/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index a1e917dd8d1..689ae1ae106 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -122,6 +122,7 @@ func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger log.Logger, m var err error cfg := NewAppOptions() + cfg.SkipFailingGenesisTxs = skipFailingGenesisTxs // Get main DB. cfg.DB, err = dbm.NewDB("gnolang", dbm.GoLevelDBBackend, filepath.Join(dataRootDir, "data")) @@ -157,7 +158,7 @@ func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank // NOTE: comment out to ignore. if !skipFailingGenesisTxs { - panic(res.Error) + panic(res.Log) } } else { ctx.Logger().Info("SUCCESS:", string(amino.MustMarshalJSON(tx))) diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index e809103469d..8f2fa0debf6 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -105,6 +105,9 @@ func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee, deposit s for _, pkg := range nonDraftPkgs { // Open files in directory as MemPackage. memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) + if err := memPkg.Validate(); err != nil { + return nil, fmt.Errorf("invalid package: %w", err) + } // Create transaction tx := std.Tx{ diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index 0edef304281..bb6dae0085f 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -2,6 +2,7 @@ package gnoland import ( "fmt" + "sync" "time" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" @@ -12,6 +13,7 @@ import ( "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/events" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gnolang/gno/tm2/pkg/std" @@ -51,7 +53,9 @@ func NewDefaultGenesisConfig(pk crypto.PubKey, chainid string) *bft.GenesisDoc { } func NewDefaultTMConfig(rootdir string) *tmcfg.Config { - return tmcfg.DefaultConfig().SetRootDir(rootdir) + // We use `TestConfig` here otherwise ChainID will be empty, and + // there is no other way to update it than using a config file + return tmcfg.TestConfig().SetRootDir(rootdir) } // NewInMemoryNodeConfig creates a default configuration for an in-memory node. @@ -145,3 +149,29 @@ func NewInMemoryNode(logger log.Logger, cfg *InMemoryNodeConfig) (*node.Node, er logger, ) } + +// GetNodeReadiness 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 GetNodeReadiness(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/gno.land/pkg/gnoweb/gnoweb.go b/gno.land/pkg/gnoweb/gnoweb.go new file mode 100644 index 00000000000..c250842fb50 --- /dev/null +++ b/gno.land/pkg/gnoweb/gnoweb.go @@ -0,0 +1,507 @@ +package gnoweb + +import ( + "embed" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "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" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gorilla/mux" + "github.com/gotuna/gotuna" + + // for static files + "github.com/gnolang/gno/gno.land/pkg/gnoweb/static" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types + // "github.com/gnolang/gno/tm2/pkg/sdk" // for baseapp (info, status) +) + +const ( + qFileStr = "vm/qfile" +) + +//go:embed views/* +var defaultViewsFiles embed.FS + +type Config struct { + RemoteAddr string + CaptchaSite string + FaucetURL string + ViewsDir string + HelpChainID string + HelpRemote string + WithAnalytics bool +} + +func NewDefaultConfig() Config { + return Config{ + RemoteAddr: "127.0.0.1:26657", + CaptchaSite: "", + FaucetURL: "http://localhost:5050", + ViewsDir: "", + HelpChainID: "dev", + HelpRemote: "127.0.0.1:26657", + WithAnalytics: false, + } +} + +func MakeApp(logger log.Logger, cfg Config) gotuna.App { + var viewFiles fs.FS + + // Get specific views directory if specified + if cfg.ViewsDir != "" { + viewFiles = os.DirFS(cfg.ViewsDir) + } else { + // Get embed views + var err error + viewFiles, err = fs.Sub(defaultViewsFiles, "views") + if err != nil { + panic("unable to get views directory from embed fs: " + err.Error()) + } + } + + app := gotuna.App{ + ViewFiles: viewFiles, + Router: gotuna.NewMuxRouter(), + Static: static.EmbeddedStatic, + } + + // 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(logger, app, &cfg, 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(logger, app, &cfg, 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(logger, app, &cfg)) + app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}", handlerRealmMain(logger, app, &cfg)) + app.Router.Handle("/r/{rlmname:[a-z][a-z0-9_]*(?:/[a-z][a-z0-9_]*)+}:{querystr:.*}", handlerRealmRender(logger, app, &cfg)) + app.Router.Handle("/p/{filepath:.*}", handlerPackageFile(logger, app, &cfg)) + + // other + app.Router.Handle("/faucet", handlerFaucet(logger, app, &cfg)) + app.Router.Handle("/static/{path:.+}", handlerStaticFile(logger, app, &cfg)) + app.Router.Handle("/favicon.ico", handlerFavicon(logger, app, &cfg)) + + // api + app.Router.Handle("/status.json", handlerStatusJSON(logger, app, &cfg)) + + app.Router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := r.RequestURI + handleNotFound(app, &cfg, path, w, r) + }) + return app +} + +// 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(logger log.Logger, app gotuna.App, cfg *Config, rlmpath string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + 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(logger, cfg, qpath, data) + if err != nil { + writeError(logger, w, fmt.Errorf("gnoweb failed to query gnoland: %w", err)) + return + } + + 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, + }) + } + + 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("Config", cfg) + tmpl.Set("IsAlias", true) + tmpl.Render(w, r, "realm_render.html", "funcs.html") + }) +} + +func handlerFaucet(logger log.Logger, app gotuna.App, cfg *Config) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + app.NewTemplatingEngine(). + Set("Config", cfg). + Render(w, r, "faucet.html", "funcs.html") + }) +} + +func handlerStatusJSON(logger log.Logger, app gotuna.App, cfg *Config) http.Handler { + startedAt := time.Now() + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ret struct { + Gnoland struct { + Connected bool `json:"connected"` + Error *string `json:"error,omitempty"` + Height *int64 `json:"height,omitempty"` + // processed txs + // active connections + + Version *string `json:"version,omitempty"` + // Uptime *float64 `json:"uptime-seconds,omitempty"` + // Goarch *string `json:"goarch,omitempty"` + // Goos *string `json:"goos,omitempty"` + // GoVersion *string `json:"go-version,omitempty"` + // NumCPU *int `json:"num_cpu,omitempty"` + } `json:"gnoland"` + Website struct { + // Version string `json:"version"` + Uptime float64 `json:"uptime-seconds"` + Goarch string `json:"goarch"` + Goos string `json:"goos"` + GoVersion string `json:"go-version"` + NumCPU int `json:"num_cpu"` + } `json:"website"` + } + ret.Website.Uptime = time.Since(startedAt).Seconds() + ret.Website.Goarch = runtime.GOARCH + ret.Website.Goos = runtime.GOOS + ret.Website.NumCPU = runtime.NumCPU() + ret.Website.GoVersion = runtime.Version() + + ret.Gnoland.Connected = true + res, err := makeRequest(logger, cfg, ".app/version", []byte{}) + if err != nil { + ret.Gnoland.Connected = false + errmsg := err.Error() + ret.Gnoland.Error = &errmsg + } else { + version := string(res.Value) + ret.Gnoland.Version = &version + ret.Gnoland.Height = &res.Height + } + + out, _ := json.MarshalIndent(ret, "", " ") + w.Header().Set("Content-Type", "application/json") + w.Write(out) + }) +} + +func handlerRedirect(logger log.Logger, app gotuna.App, cfg *Config, to string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, to, http.StatusFound) + tmpl := app.NewTemplatingEngine() + tmpl.Set("To", to) + tmpl.Set("Config", cfg) + tmpl.Render(w, r, "redirect.html", "funcs.html") + }) +} + +func handlerRealmMain(logger log.Logger, app gotuna.App, cfg *Config) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rlmname := vars["rlmname"] + rlmpath := "gno.land/r/" + rlmname + query := r.URL.Query() + + logger.Info("handling", "name", rlmname, "path", rlmpath) + if query.Has("help") { + // Render function helper. + funcName := query.Get("__func") + qpath := "vm/qfuncs" + data := []byte(rlmpath) + res, err := makeRequest(logger, cfg, qpath, data) + if err != nil { + writeError(logger, w, fmt.Errorf("request failed: %w", err)) + return + } + var fsigs vm.FunctionSignatures + amino.MustUnmarshalJSON(res.Data, &fsigs) + // Fill fsigs with query parameters. + for i := range fsigs { + fsig := &(fsigs[i]) + for j := range fsig.Params { + param := &(fsig.Params[j]) + value := query.Get(param.Name) + param.Value = value + } + } + // Render template. + tmpl := app.NewTemplatingEngine() + tmpl.Set("FuncName", funcName) + tmpl.Set("RealmPath", rlmpath) + tmpl.Set("DirPath", pathOf(rlmpath)) + tmpl.Set("FunctionSignatures", fsigs) + tmpl.Set("Config", cfg) + tmpl.Render(w, r, "realm_help.html", "funcs.html") + } else { + // Ensure realm exists. TODO optimize. + qpath := qFileStr + data := []byte(rlmpath) + _, err := makeRequest(logger, cfg, qpath, data) + if err != nil { + writeError(logger, w, errors.New("error querying realm package")) + return + } + // Render blank query path, /r/REALM:. + handleRealmRender(logger, app, cfg, w, r) + } + }) +} + +type pathLink struct { + URL string + Text string +} + +func handlerRealmRender(logger log.Logger, app gotuna.App, cfg *Config) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handleRealmRender(logger, app, cfg, w, r) + }) +} + +func handleRealmRender(logger log.Logger, app gotuna.App, cfg *Config, w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rlmname := vars["rlmname"] + rlmpath := "gno.land/r/" + rlmname + querystr := vars["querystr"] + if r.URL.Path == "/r/"+rlmname+":" { + // Redirect to /r/REALM if querypath is empty. + http.Redirect(w, r, "/r/"+rlmname, http.StatusFound) + return + } + qpath := "vm/qrender" + data := []byte(fmt.Sprintf("%s\n%s", rlmpath, querystr)) + res, err := makeRequest(logger, cfg, qpath, data) + if err != nil { + // XXX hack + if strings.Contains(err.Error(), "Render not declared") { + res = &abci.ResponseQuery{} + res.Data = []byte("realm package has no Render() function") + } else { + writeError(logger, w, err) + return + } + } + // linkify querystr. + 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, + }) + } + // 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("Config", cfg) + tmpl.Render(w, r, "realm_render.html", "funcs.html") +} + +func handlerRealmFile(logger log.Logger, app gotuna.App, cfg *Config) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + diruri := "gno.land/r/" + vars["rlmname"] + filename := vars["filename"] + renderPackageFile(logger, app, cfg, w, r, diruri, filename) + }) +} + +func handlerPackageFile(logger log.Logger, app gotuna.App, cfg *Config) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + pkgpath := "gno.land/p/" + vars["filepath"] + diruri, filename := std.SplitFilepath(pkgpath) + if filename == "" && diruri == pkgpath { + // redirect to diruri + "/" + http.Redirect(w, r, "/p/"+vars["filepath"]+"/", http.StatusFound) + return + } + renderPackageFile(logger, app, cfg, w, r, diruri, filename) + }) +} + +func renderPackageFile(logger log.Logger, app gotuna.App, cfg *Config, w http.ResponseWriter, r *http.Request, diruri string, filename string) { + if filename == "" { + // Request is for a folder. + qpath := qFileStr + data := []byte(diruri) + res, err := makeRequest(logger, cfg, qpath, data) + if err != nil { + writeError(logger, w, err) + return + } + files := strings.Split(string(res.Data), "\n") + // Render template. + tmpl := app.NewTemplatingEngine() + tmpl.Set("DirURI", diruri) + tmpl.Set("DirPath", pathOf(diruri)) + tmpl.Set("Files", files) + tmpl.Set("Config", cfg) + tmpl.Render(w, r, "package_dir.html", "funcs.html") + } else { + // Request is for a file. + filepath := diruri + "/" + filename + qpath := qFileStr + data := []byte(filepath) + res, err := makeRequest(logger, cfg, qpath, data) + if err != nil { + writeError(logger, w, err) + return + } + // Render template. + tmpl := app.NewTemplatingEngine() + tmpl.Set("DirURI", diruri) + tmpl.Set("DirPath", pathOf(diruri)) + tmpl.Set("FileName", filename) + tmpl.Set("FileContents", string(res.Data)) + tmpl.Set("Config", cfg) + tmpl.Render(w, r, "package_file.html", "funcs.html") + } +} + +func makeRequest(log log.Logger, cfg *Config, qpath string, data []byte) (res *abci.ResponseQuery, err error) { + opts2 := client.ABCIQueryOptions{ + // Height: height, XXX + // Prove: false, XXX + } + remote := cfg.RemoteAddr + cli := client.NewHTTP(remote, "/websocket") + qres, err := cli.ABCIQueryWithOptions( + qpath, data, opts2) + if err != nil { + log.Error("request error", "path", qpath, "error", err) + return nil, fmt.Errorf("unable to query path %q: %w", qpath, err) + } + if qres.Response.Error != nil { + log.Error("response error", "path", qpath, "log", qres.Response.Log) + return nil, qres.Response.Error + } + return &qres.Response, nil +} + +func handlerStaticFile(logger log.Logger, app gotuna.App, cfg *Config) http.Handler { + fs := http.FS(app.Static) + fileapp := http.StripPrefix("/static", http.FileServer(fs)) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + fpath := filepath.Clean(vars["path"]) + f, err := fs.Open(fpath) + if os.IsNotExist(err) { + handleNotFound(app, cfg, fpath, w, r) + return + } + stat, err := f.Stat() + if err != nil || stat.IsDir() { + handleNotFound(app, cfg, fpath, w, r) + return + } + + // TODO: ModTime doesn't work for embed? + // w.Header().Set("ETag", fmt.Sprintf("%x", stat.ModTime().UnixNano())) + // w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%s", "31536000")) + fileapp.ServeHTTP(w, r) + }) +} + +func handlerFavicon(logger log.Logger, app gotuna.App, cfg *Config) http.Handler { + fs := http.FS(app.Static) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fpath := "img/favicon.ico" + f, err := fs.Open(fpath) + if os.IsNotExist(err) { + handleNotFound(app, cfg, fpath, w, r) + return + } + w.Header().Set("Content-Type", "image/x-icon") + w.Header().Set("Cache-Control", "public, max-age=604800") // 7d + io.Copy(w, f) + }) +} + +func handleNotFound(app gotuna.App, cfg *Config, path string, w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + app.NewTemplatingEngine(). + Set("title", "Not found"). + Set("path", path). + Set("Config", cfg). + Render(w, r, "404.html", "funcs.html") +} + +func writeError(logger log.Logger, w http.ResponseWriter, err error) { + if details := errors.Unwrap(err); details != nil { + logger.Error("handler", "error", err, "details", details) + } else { + logger.Error("handler", "error:", err) + } + + // XXX: writeError should return an error page template. + w.WriteHeader(500) + w.Write([]byte(err.Error())) +} + +func pathOf(diruri string) string { + parts := strings.Split(diruri, "/") + if parts[0] == "gno.land" { + return "/" + strings.Join(parts[1:], "/") + } else { + panic(fmt.Sprintf("invalid dir-URI %q", diruri)) + } +} diff --git a/gno.land/pkg/gnoweb/gnoweb_test.go b/gno.land/pkg/gnoweb/gnoweb_test.go new file mode 100644 index 00000000000..61f7244141e --- /dev/null +++ b/gno.land/pkg/gnoweb/gnoweb_test.go @@ -0,0 +1,131 @@ +package gnoweb + +import ( + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gotuna/gotuna/test/assert" +) + +func TestRoutes(t *testing.T) { + const ( + ok = http.StatusOK + found = http.StatusFound + notFound = http.StatusNotFound + ) + routes := []struct { + route string + status int + substring string + }{ + {"/", ok, "Welcome"}, // assert / gives 200 (OK). assert / contains "Welcome". + {"/about", ok, "blockchain"}, + {"/r/gnoland/blog", ok, ""}, // whatever content + {"/r/gnoland/blog?help", ok, "exposed"}, + {"/r/gnoland/blog/", ok, "admin.gno"}, + {"/r/gnoland/blog/admin.gno", ok, "func "}, + {"/r/demo/users:administrator", ok, "address"}, + {"/r/demo/users", ok, "manfred"}, + {"/r/demo/users/types.gno", ok, "type "}, + {"/r/demo/deep/very/deep", ok, "it works!"}, + {"/r/demo/deep/very/deep:bob", ok, "hi bob"}, + {"/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"}, + } + + config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNopLogger(), config) + defer node.Stop() + + cfg := NewDefaultConfig() + + logger := log.TestingLogger() + + // set the `remoteAddr` of the client to the listening address of the + // node, which is randomly assigned. + cfg.RemoteAddr = remoteAddr + app := MakeApp(logger, cfg) + + for _, r := range routes { + t.Run(fmt.Sprintf("test route %s", r.route), func(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, r.route, nil) + response := httptest.NewRecorder() + app.Router.ServeHTTP(response, request) + assert.Equal(t, r.status, response.Code) + + 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, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNopLogger(), config) + defer node.Stop() + + cfg := NewDefaultConfig() + cfg.RemoteAddr = remoteAddr + + logger := log.TestingLogger() + + t.Run("with", func(t *testing.T) { + for _, route := range routes { + t.Run(route, func(t *testing.T) { + ccfg := cfg // clone config + ccfg.WithAnalytics = true + app := MakeApp(logger, ccfg) + 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) { + ccfg := cfg // clone config + ccfg.WithAnalytics = false + app := MakeApp(logger, ccfg) + 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/static/css/app.css b/gno.land/pkg/gnoweb/static/css/app.css similarity index 100% rename from gno.land/cmd/gnoweb/static/css/app.css rename to gno.land/pkg/gnoweb/static/css/app.css diff --git a/gno.land/cmd/gnoweb/static/css/normalize.css b/gno.land/pkg/gnoweb/static/css/normalize.css similarity index 100% rename from gno.land/cmd/gnoweb/static/css/normalize.css rename to gno.land/pkg/gnoweb/static/css/normalize.css diff --git a/gno.land/cmd/gnoweb/static/font/README.md b/gno.land/pkg/gnoweb/static/font/README.md similarity index 100% rename from gno.land/cmd/gnoweb/static/font/README.md rename to gno.land/pkg/gnoweb/static/font/README.md diff --git a/gno.land/cmd/gnoweb/static/font/roboto/LICENSE.txt b/gno.land/pkg/gnoweb/static/font/roboto/LICENSE.txt similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/LICENSE.txt rename to gno.land/pkg/gnoweb/static/font/roboto/LICENSE.txt diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Bold.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Bold.woff similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Bold.woff rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Bold.woff diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-BoldItalic.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-BoldItalic.woff similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-BoldItalic.woff rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-BoldItalic.woff diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Italic.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Italic.woff similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Italic.woff rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Italic.woff diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Light.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Light.woff similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Light.woff rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Light.woff diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-LightItalic.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-LightItalic.woff similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-LightItalic.woff rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-LightItalic.woff diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Medium.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Medium.woff similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Medium.woff rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Medium.woff diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-MediumItalic.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-MediumItalic.woff similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-MediumItalic.woff rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-MediumItalic.woff diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Regular.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Regular.woff similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Regular.woff rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Regular.woff diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Thin.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Thin.woff similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-Thin.woff rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-Thin.woff diff --git a/gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-ThinItalic.woff b/gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-ThinItalic.woff similarity index 100% rename from gno.land/cmd/gnoweb/static/font/roboto/RobotoMono-ThinItalic.woff rename to gno.land/pkg/gnoweb/static/font/roboto/RobotoMono-ThinItalic.woff diff --git a/gno.land/cmd/gnoweb/static/img/favicon.ico b/gno.land/pkg/gnoweb/static/img/favicon.ico similarity index 100% rename from gno.land/cmd/gnoweb/static/img/favicon.ico rename to gno.land/pkg/gnoweb/static/img/favicon.ico diff --git a/gno.land/cmd/gnoweb/static/img/github-mark-32px.png b/gno.land/pkg/gnoweb/static/img/github-mark-32px.png similarity index 100% rename from gno.land/cmd/gnoweb/static/img/github-mark-32px.png rename to gno.land/pkg/gnoweb/static/img/github-mark-32px.png diff --git a/gno.land/cmd/gnoweb/static/img/github-mark-64px.png b/gno.land/pkg/gnoweb/static/img/github-mark-64px.png similarity index 100% rename from gno.land/cmd/gnoweb/static/img/github-mark-64px.png rename to gno.land/pkg/gnoweb/static/img/github-mark-64px.png diff --git a/gno.land/cmd/gnoweb/static/img/ico-discord.svg b/gno.land/pkg/gnoweb/static/img/ico-discord.svg similarity index 100% rename from gno.land/cmd/gnoweb/static/img/ico-discord.svg rename to gno.land/pkg/gnoweb/static/img/ico-discord.svg diff --git a/gno.land/cmd/gnoweb/static/img/ico-email.svg b/gno.land/pkg/gnoweb/static/img/ico-email.svg similarity index 100% rename from gno.land/cmd/gnoweb/static/img/ico-email.svg rename to gno.land/pkg/gnoweb/static/img/ico-email.svg diff --git a/gno.land/cmd/gnoweb/static/img/ico-telegram.svg b/gno.land/pkg/gnoweb/static/img/ico-telegram.svg similarity index 100% rename from gno.land/cmd/gnoweb/static/img/ico-telegram.svg rename to gno.land/pkg/gnoweb/static/img/ico-telegram.svg diff --git a/gno.land/cmd/gnoweb/static/img/ico-twitter.svg b/gno.land/pkg/gnoweb/static/img/ico-twitter.svg similarity index 100% rename from gno.land/cmd/gnoweb/static/img/ico-twitter.svg rename to gno.land/pkg/gnoweb/static/img/ico-twitter.svg diff --git a/gno.land/cmd/gnoweb/static/img/ico-youtube.svg b/gno.land/pkg/gnoweb/static/img/ico-youtube.svg similarity index 100% rename from gno.land/cmd/gnoweb/static/img/ico-youtube.svg rename to gno.land/pkg/gnoweb/static/img/ico-youtube.svg diff --git a/gno.land/cmd/gnoweb/static/img/list-alt.png b/gno.land/pkg/gnoweb/static/img/list-alt.png similarity index 100% rename from gno.land/cmd/gnoweb/static/img/list-alt.png rename to gno.land/pkg/gnoweb/static/img/list-alt.png diff --git a/gno.land/cmd/gnoweb/static/img/list.png b/gno.land/pkg/gnoweb/static/img/list.png similarity index 100% rename from gno.land/cmd/gnoweb/static/img/list.png rename to gno.land/pkg/gnoweb/static/img/list.png diff --git a/gno.land/cmd/gnoweb/static/img/logo-square.png b/gno.land/pkg/gnoweb/static/img/logo-square.png similarity index 100% rename from gno.land/cmd/gnoweb/static/img/logo-square.png rename to gno.land/pkg/gnoweb/static/img/logo-square.png diff --git a/gno.land/cmd/gnoweb/static/img/logo-square.svg b/gno.land/pkg/gnoweb/static/img/logo-square.svg similarity index 100% rename from gno.land/cmd/gnoweb/static/img/logo-square.svg rename to gno.land/pkg/gnoweb/static/img/logo-square.svg diff --git a/gno.land/cmd/gnoweb/static/img/logo-v1.png b/gno.land/pkg/gnoweb/static/img/logo-v1.png similarity index 100% rename from gno.land/cmd/gnoweb/static/img/logo-v1.png rename to gno.land/pkg/gnoweb/static/img/logo-v1.png diff --git a/gno.land/cmd/gnoweb/static/img/logo.png b/gno.land/pkg/gnoweb/static/img/logo.png similarity index 100% rename from gno.land/cmd/gnoweb/static/img/logo.png rename to gno.land/pkg/gnoweb/static/img/logo.png diff --git a/gno.land/cmd/gnoweb/static/img/logo.svg b/gno.land/pkg/gnoweb/static/img/logo.svg similarity index 100% rename from gno.land/cmd/gnoweb/static/img/logo.svg rename to gno.land/pkg/gnoweb/static/img/logo.svg diff --git a/gno.land/cmd/gnoweb/static/invites.txt b/gno.land/pkg/gnoweb/static/invites.txt similarity index 100% rename from gno.land/cmd/gnoweb/static/invites.txt rename to gno.land/pkg/gnoweb/static/invites.txt diff --git a/gno.land/cmd/gnoweb/static/js/highlight.min.js b/gno.land/pkg/gnoweb/static/js/highlight.min.js similarity index 100% rename from gno.land/cmd/gnoweb/static/js/highlight.min.js rename to gno.land/pkg/gnoweb/static/js/highlight.min.js diff --git a/gno.land/cmd/gnoweb/static/js/marked.min.js b/gno.land/pkg/gnoweb/static/js/marked.min.js similarity index 100% rename from gno.land/cmd/gnoweb/static/js/marked.min.js rename to gno.land/pkg/gnoweb/static/js/marked.min.js diff --git a/gno.land/cmd/gnoweb/static/js/purify.min.js b/gno.land/pkg/gnoweb/static/js/purify.min.js similarity index 100% rename from gno.land/cmd/gnoweb/static/js/purify.min.js rename to gno.land/pkg/gnoweb/static/js/purify.min.js diff --git a/gno.land/cmd/gnoweb/static/js/realm_help.js b/gno.land/pkg/gnoweb/static/js/realm_help.js similarity index 100% rename from gno.land/cmd/gnoweb/static/js/realm_help.js rename to gno.land/pkg/gnoweb/static/js/realm_help.js diff --git a/gno.land/cmd/gnoweb/static/js/renderer.js b/gno.land/pkg/gnoweb/static/js/renderer.js similarity index 100% rename from gno.land/cmd/gnoweb/static/js/renderer.js rename to gno.land/pkg/gnoweb/static/js/renderer.js diff --git a/gno.land/cmd/gnoweb/static/js/umbrella.js b/gno.land/pkg/gnoweb/static/js/umbrella.js similarity index 99% rename from gno.land/cmd/gnoweb/static/js/umbrella.js rename to gno.land/pkg/gnoweb/static/js/umbrella.js index 2a02a61f6f3..9735d031265 100644 --- a/gno.land/cmd/gnoweb/static/js/umbrella.js +++ b/gno.land/pkg/gnoweb/static/js/umbrella.js @@ -617,7 +617,7 @@ u.prototype.scroll = function () { // [INTERNAL USE ONLY] -// Select the adecuate part from the context +// Select the adequate part from the context u.prototype.select = function (parameter, context) { // Allow for spaces before or after parameter = parameter.replace(/^\s*/, '').replace(/\s*$/, ''); diff --git a/gno.land/cmd/gnoweb/static/js/umbrella.min.js b/gno.land/pkg/gnoweb/static/js/umbrella.min.js similarity index 100% rename from gno.land/cmd/gnoweb/static/js/umbrella.min.js rename to gno.land/pkg/gnoweb/static/js/umbrella.min.js diff --git a/gno.land/cmd/gnoweb/static/static.go b/gno.land/pkg/gnoweb/static/static.go similarity index 100% rename from gno.land/cmd/gnoweb/static/static.go rename to gno.land/pkg/gnoweb/static/static.go diff --git a/gno.land/cmd/gnoweb/views/404.html b/gno.land/pkg/gnoweb/views/404.html similarity index 100% rename from gno.land/cmd/gnoweb/views/404.html rename to gno.land/pkg/gnoweb/views/404.html diff --git a/gno.land/cmd/gnoweb/views/faucet.html b/gno.land/pkg/gnoweb/views/faucet.html similarity index 95% rename from gno.land/cmd/gnoweb/views/faucet.html rename to gno.land/pkg/gnoweb/views/faucet.html index 85d3d6780c5..36c5987b9c9 100644 --- a/gno.land/cmd/gnoweb/views/faucet.html +++ b/gno.land/pkg/gnoweb/views/faucet.html @@ -20,7 +20,7 @@ {{ end }} -
+
{{ if .Data.captchaSite }} diff --git a/gno.land/cmd/gnoweb/views/funcs.html b/gno.land/pkg/gnoweb/views/funcs.html similarity index 99% rename from gno.land/cmd/gnoweb/views/funcs.html rename to gno.land/pkg/gnoweb/views/funcs.html index c8d643ef655..391675755aa 100644 --- a/gno.land/cmd/gnoweb/views/funcs.html +++ b/gno.land/pkg/gnoweb/views/funcs.html @@ -155,7 +155,7 @@ {{- end -}} {{- define "analytics" -}} -{{- if .Data.Flags.WithAnalytics -}} +{{- if .Data.Config.WithAnalytics -}} diff --git a/gno.land/cmd/gnoweb/views/generic.html b/gno.land/pkg/gnoweb/views/generic.html similarity index 100% rename from gno.land/cmd/gnoweb/views/generic.html rename to gno.land/pkg/gnoweb/views/generic.html diff --git a/gno.land/cmd/gnoweb/views/package_dir.html b/gno.land/pkg/gnoweb/views/package_dir.html similarity index 100% rename from gno.land/cmd/gnoweb/views/package_dir.html rename to gno.land/pkg/gnoweb/views/package_dir.html diff --git a/gno.land/cmd/gnoweb/views/package_file.html b/gno.land/pkg/gnoweb/views/package_file.html similarity index 100% rename from gno.land/cmd/gnoweb/views/package_file.html rename to gno.land/pkg/gnoweb/views/package_file.html diff --git a/gno.land/cmd/gnoweb/views/realm_help.html b/gno.land/pkg/gnoweb/views/realm_help.html similarity index 92% rename from gno.land/cmd/gnoweb/views/realm_help.html rename to gno.land/pkg/gnoweb/views/realm_help.html index d043fd1dcd6..84314a1d855 100644 --- a/gno.land/cmd/gnoweb/views/realm_help.html +++ b/gno.land/pkg/gnoweb/views/realm_help.html @@ -7,7 +7,7 @@
-
+
{{ .Data.DirPath }}?help @@ -17,7 +17,7 @@
These are the realm's exposed functions ("public smart contracts").

- My address: (see `gnokey list`)
+ My address: (see `gnokey list`)


{{ template "func_specs" . }} diff --git a/gno.land/cmd/gnoweb/views/realm_render.html b/gno.land/pkg/gnoweb/views/realm_render.html similarity index 100% rename from gno.land/cmd/gnoweb/views/realm_render.html rename to gno.land/pkg/gnoweb/views/realm_render.html diff --git a/gno.land/cmd/gnoweb/views/redirect.html b/gno.land/pkg/gnoweb/views/redirect.html similarity index 100% rename from gno.land/cmd/gnoweb/views/redirect.html rename to gno.land/pkg/gnoweb/views/redirect.html diff --git a/gno.land/pkg/integration/testing_node.go b/gno.land/pkg/integration/testing_node.go index 1ca7e11eb63..4705f4e3d73 100644 --- a/gno.land/pkg/integration/testing_node.go +++ b/gno.land/pkg/integration/testing_node.go @@ -2,7 +2,6 @@ package integration import ( "path/filepath" - "sync" "time" "github.com/gnolang/gno/gno.land/pkg/gnoland" @@ -11,7 +10,6 @@ import ( "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" @@ -33,7 +31,7 @@ func TestingInMemoryNode(t TestingTS, logger log.Logger, config *gnoland.InMemor require.NoError(t, err) select { - case <-waitForNodeReadiness(node): + case <-gnoland.GetNodeReadiness(node): case <-time.After(time.Second * 6): require.FailNow(t, "timeout while waiting for the node to start") } @@ -116,8 +114,7 @@ func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []std 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) + txs, err := gnoland.LoadPackagesFromDir(examplesDir, creator, defaultFee, nil) require.NoError(t, err) return txs @@ -156,29 +153,3 @@ func DefaultTestingTMConfig(gnoroot string) *tmcfg.Config { 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/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 270a4f7c37e..ef2ac93f617 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -40,47 +40,10 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { return m2.RunMemPackage(memPkg, true) } store.SetPackageGetter(getPackage) - store.SetPackageInjector(vm.packageInjector) + store.SetNativeStore(stdlibs.NativeStore) stdlibs.InjectNativeMappings(store) } -func (vm *VMKeeper) packageInjector(store gno.Store, pn *gno.PackageNode) { - // Also inject stdlibs native functions. - stdlibs.InjectPackage(store, pn) - // vm (this package) specific injections: - switch pn.PkgPath { - case "std": - /* XXX deleteme - // Also see stdlibs/InjectPackage. - pn.DefineNative("AssertOriginCall", - gno.Flds( // params - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - isOrigin := len(m.Frames) == 2 - if !isOrigin { - panic("invalid non-origin call") - } - }, - ) - pn.DefineNative("IsOriginCall", - gno.Flds( // params - ), - gno.Flds( // results - "isOrigin", "bool", - ), - func(m *gno.Machine) { - isOrigin := len(m.Frames) == 2 - res0 := gno.TypedValue{T: gno.BoolType} - res0.SetBool(isOrigin) - m.PushValue(res0) - }, - ) - */ - } -} - // ---------------------------------------- // SDKBanker diff --git a/gnovm/Makefile b/gnovm/Makefile index 29510e9a1da..cc7154492d8 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -63,6 +63,11 @@ _test.gnolang.stdlibs.sync:; go test tests/*.go -test.short -run 'TestFiles$$/' ######################################## # Code gen +# TODO: move _dev.stringer to go:generate instructions, simplify generate +# to just go generate. +.PHONY: generate +generate: _dev.stringer _dev.generate + stringer_cmd=$(rundep) golang.org/x/tools/cmd/stringer .PHONY: _dev.stringer _dev.stringer: @@ -73,5 +78,9 @@ _dev.stringer: $(stringer_cmd) -type=VPType ./pkg/gnolang $(stringer_cmd) -type=Word ./pkg/gnolang +.PHONY: _dev.generate +_dev.generate: + go generate -x ./... + # genproto: # see top-level Makefile. diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 38615c88c4e..86ca33f3dc8 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -313,8 +313,11 @@ func gnoTestPkg( if err == nil { gnoPkgPath = modfile.Module.Mod.Path } else { - // unable to read pkgPath from gno.mod, generate a random realm path - gnoPkgPath = gno.GnoRealmPkgsPrefixBefore + random.RandStr(8) + gnoPkgPath = pkgPathFromRootDir(pkgPath, rootDir) + if gnoPkgPath == "" { + // unable to read pkgPath from gno.mod, generate a random realm path + gnoPkgPath = gno.GnoRealmPkgsPrefixBefore + random.RandStr(8) + } } memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath) @@ -431,6 +434,35 @@ func gnoTestPkg( return errs } +// attempts to determine the full gno pkg path by analyzing the directory. +func pkgPathFromRootDir(pkgPath, rootDir string) string { + abPkgPath, err := filepath.Abs(pkgPath) + if err != nil { + log.Printf("could not determine abs path: %v", err) + return "" + } + abRootDir, err := filepath.Abs(rootDir) + if err != nil { + log.Printf("could not determine abs path: %v", err) + return "" + } + abRootDir += string(filepath.Separator) + if !strings.HasPrefix(abPkgPath, abRootDir) { + return "" + } + impPath := strings.ReplaceAll(abPkgPath[len(abRootDir):], string(filepath.Separator), "/") + for _, prefix := range [...]string{ + "examples/", + "gnovm/stdlibs/", + "gnovm/tests/stdlibs/", + } { + if strings.HasPrefix(impPath, prefix) { + return impPath[len(prefix):] + } + } + return "" +} + func runTestFiles( m *gno.Machine, files *gno.FileSet, diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar index c3d3b983e34..f17f28055f2 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar @@ -55,6 +55,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/x", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar index 3922c34fec8..38794a3e645 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar @@ -7,7 +7,7 @@ stderr '=== RUN file/x_filetest.gno' stderr 'panic: fail on x_filetest.gno: diff:' stderr '--- Expected' stderr '\+\+\+ Actual' -stderr '@@ -1 \+1,64 @@' +stderr '@@ -1 \+1,66 @@' stderr '-xxx' stderr '\+switchrealm\["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 236e69f8641..e8c643af0ba 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar @@ -70,6 +70,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/x", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index 37b590b1c4c..5c50b0830a6 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -16,7 +16,9 @@ func TestRunEmptyMain(t *testing.T) { t.Parallel() m := NewMachine("test", nil) - main := FuncD("main", nil, nil, nil) + // []Stmt{} != nil, as nil means that in the source code not even the + // brackets are present and is reserved for external (ie. native) functions. + main := FuncD("main", nil, nil, []Stmt{}) m.RunDeclaration(main) m.RunMain() } @@ -39,6 +41,75 @@ func main() { m.RunMain() } +func TestDoOpEvalBaseConversion(t *testing.T) { + m := NewMachine("test", nil) + + type testCase struct { + input string + expect string + expectErr bool + } + + testCases := []testCase{ + // binary + {input: "0b101010", expect: "42", expectErr: false}, + {input: "0B101010", expect: "42", expectErr: false}, + {input: "0b111111111111111111111111111111111111111111111111111111111111111", expect: "9223372036854775807", expectErr: false}, + {input: "0b0", expect: "0", expectErr: false}, + {input: "0b000000101010", expect: "42", expectErr: false}, + {input: " 0b101010", expectErr: true}, + {input: "0b", expectErr: true}, + {input: "0bXXXX", expectErr: true}, + {input: "42b0", expectErr: true}, + // octal + {input: "0o42", expect: "34", expectErr: false}, + {input: "0o0", expect: "0", expectErr: false}, + {input: "042", expect: "34", expectErr: false}, + {input: "0777", expect: "511", expectErr: false}, + {input: "0O0000042", expect: "34", expectErr: false}, + {input: "0777777777777777777777", expect: "9223372036854775807", expectErr: false}, + {input: "0o777777777777777777777", expect: "9223372036854775807", expectErr: false}, + {input: "048", expectErr: true}, + {input: "0o", expectErr: true}, + {input: "0oXXXX", expectErr: true}, + {input: "0OXXXX", expectErr: true}, + {input: "0o42x42", expectErr: true}, + {input: "0O42x42", expectErr: true}, + {input: "0420x42", expectErr: true}, + {input: "0o420o42", expectErr: true}, + // hex + {input: "0x2a", expect: "42", expectErr: false}, + {input: "0X2A", expect: "42", expectErr: false}, + {input: "0x7FFFFFFFFFFFFFFF", expect: "9223372036854775807", expectErr: false}, + {input: "0x2a ", expectErr: true}, + {input: "0x", expectErr: true}, + {input: "0xXXXX", expectErr: true}, + {input: "0xGHIJ", expectErr: true}, + {input: "0x42o42", expectErr: true}, + {input: "0x2ax42", expectErr: true}, + // decimal + {input: "42", expect: "42", expectErr: false}, + {input: "0", expect: "0", expectErr: false}, + {input: "0000000000", expect: "0", expectErr: false}, + {input: "9223372036854775807", expect: "9223372036854775807", expectErr: false}, + } + + for _, tc := range testCases { + m.PushExpr(&BasicLitExpr{ + Kind: INT, + Value: tc.input, + }) + + if tc.expectErr { + assert.Panics(t, func() { m.doOpEval() }) + } else { + m.doOpEval() + v := m.PopValue() + assert.Equal(t, v.V.String(), tc.expect) + } + } +} + func TestEval(t *testing.T) { t.Parallel() diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index c3ab4e95b73..ee82cb39555 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -96,9 +96,11 @@ func MustParseExpr(expr string) Expr { return x } -// filename must not include the path. +// ParseFile uses the Go parser to parse body. It then runs [Go2Gno] on the +// resulting AST -- the resulting FileNode is returned, together with any other +// error (including panics, which are recovered) from [Go2Gno]. func ParseFile(filename string, body string) (fn *FileNode, err error) { - // Parse src but stop after processing the imports. + // Use go parser to parse the body. fs := token.NewFileSet() f, err := parser.ParseFile(fs, filename, body, parser.ParseComments|parser.DeclarationErrors) if err != nil { @@ -112,9 +114,9 @@ func ParseFile(filename string, body string) (fn *FileNode, err error) { defer func() { if r := recover(); r != nil { if rerr, ok := r.(error); ok { - err = rerr + err = errors.Wrap(rerr, "parsing file") } else { - err = errors.New(fmt.Sprintf("%v", r)) + err = errors.New(fmt.Sprintf("%v", r)).Stacktrace() } return } @@ -431,7 +433,10 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } name := toName(gon.Name) type_ := Go2Gno(fs, gon.Type).(*FuncTypeExpr) - body := Go2Gno(fs, gon.Body).(*BlockStmt).Body + var body []Stmt + if gon.Body != nil { + body = Go2Gno(fs, gon.Body).(*BlockStmt).Body + } return &FuncDecl{ IsMethod: isMethod, Recv: recv, diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 69e468f755a..f3739dad0f3 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -676,6 +676,9 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv return } +// used for direct comparison to error types +var tError = reflect.TypeOf(new(error)).Elem() + // If recursive is false, this function is like go2GnoValue() but less lazy // (but still not recursive/eager). When recursive is false, it is for // converting Go types to Gno types upon an explicit conversion (via @@ -757,6 +760,15 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo // regardless. tv.V = alloc.NewNative(rv) case reflect.Interface: + // special case for errors, which are very often used especially in + // native bindings + if rv.Type() == tError { + tv.T = gErrorType + if !rv.IsNil() { + tv.V = alloc.NewNative(rv.Elem()) + } + return + } panic("not yet implemented") case reflect.Map: panic("not yet implemented") diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go index 4810a67304a..b163b6a52a7 100644 --- a/gnovm/pkg/gnolang/helpers.go +++ b/gnovm/pkg/gnolang/helpers.go @@ -105,6 +105,12 @@ func MaybeNativeT(tx interface{}) *MaybeNativeTypeExpr { } } +// FuncD creates a new function declaration. +// +// There is a difference between passing nil to body or passing []Stmt{}: +// nil means that the curly brackets are missing in the source code, indicating +// a declaration for an externally-defined function, while []Stmt{} is simply a +// functions with no statements (func() {}). func FuncD(name interface{}, params, results FieldTypeExprs, body []Stmt) *FuncDecl { return &FuncDecl{ NameExpr: *Nx(name), diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 94232e014d2..f032ecd1d0d 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -46,14 +46,15 @@ type Machine struct { Context interface{} } -// machine.Release() must be called on objects -// created via this constructor -// Machine with new package of given path. -// Creates a new MemRealmer for any new realms. -// Looks in store for package of pkgPath; if not found, -// creates new instances as necessary. -// If pkgPath is zero, the machine has no active package -// and one must be set prior to usage. +// NewMachine initializes a new gno virtual machine, acting as a shorthand +// for [NewMachineWithOptions], setting the given options PkgPath and Store. +// +// The machine will run on the package at the given path, which will be +// retrieved through the given store. If it is not set, the machine has no +// active package, and one must be set prior to usage. +// +// Like for [NewMachineWithOptions], Machines initialized through this +// constructor must be finalized with [Machine.Release]. func NewMachine(pkgPath string, store Store) *Machine { return NewMachineWithOptions( MachineOptions{ @@ -62,12 +63,14 @@ func NewMachine(pkgPath string, store Store) *Machine { }) } +// MachineOptions is used to pass options to [NewMachineWithOptions]. type MachineOptions struct { + // Active package of the given machine; must be set before execution. PkgPath string CheckTypes bool // not yet used ReadOnly bool - Output io.Writer - Store Store + Output io.Writer // default os.Stdout + Store Store // default NewStore(Alloc, nil, nil) Context interface{} Alloc *Allocator // or see MaxAllocBytes. MaxAllocBytes int64 // or 0 for no limit. @@ -87,6 +90,11 @@ var machinePool = sync.Pool{ }, } +// NewMachineWithOptions initializes a new gno virtual machine with the given +// options. +// +// Machines initialized through this constructor must be finalized with +// [Machine.Release]. func NewMachineWithOptions(opts MachineOptions) *Machine { checkTypes := opts.CheckTypes readOnly := opts.ReadOnly @@ -141,11 +149,10 @@ var ( valueZeroed [VMSliceSize]TypedValue ) -// m should not be used after this call -// if m is nil, this will panic -// this is on purpose, to discourage misuse -// and prevent objects that were not taken from -// the pool, to call Release +// Release resets some of the values of *Machine and puts back m into the +// machine pool; for this reason, Release() should be called as a finalizer, +// and m should not be used after this call. Only Machines initialized with this +// package's constructors should be released. func (m *Machine) Release() { // here we zero in the values for the next user m.NumOps = 0 @@ -175,6 +182,9 @@ func (m *Machine) SetActivePackage(pv *PackageValue) { // Upon restart, preprocess all MemPackage and save blocknodes. // This is a temporary measure until we optimize/make-lazy. +// +// NOTE: package paths not beginning with gno.land will be allowed to override, +// to support cases of stdlibs processed through [RunMemPackagesWithOverrides]. func (m *Machine) PreprocessAllFilesAndSaveBlockNodes() { ch := m.Store.IterMemPackage() for memPkg := range ch { @@ -209,8 +219,23 @@ func (m *Machine) PreprocessAllFilesAndSaveBlockNodes() { // and corresponding package node, package value, and types to store. Save // is set to false for tests where package values may be native. func (m *Machine) RunMemPackage(memPkg *std.MemPackage, save bool) (*PackageNode, *PackageValue) { + return m.runMemPackage(memPkg, save, false) +} + +// RunMemPackageWithOverrides works as [RunMemPackage], however after parsing, +// declarations are filtered removing duplicate declarations. +// To control which declaration overrides which, use [ReadMemPackageFromList], +// putting the overrides at the top of the list. +func (m *Machine) RunMemPackageWithOverrides(memPkg *std.MemPackage, save bool) (*PackageNode, *PackageValue) { + return m.runMemPackage(memPkg, save, true) +} + +func (m *Machine) runMemPackage(memPkg *std.MemPackage, save, overrides bool) (*PackageNode, *PackageValue) { // parse files. files := ParseMemPackage(memPkg) + if !overrides && checkDuplicates(files) { + panic(fmt.Errorf("running package %q: duplicate declarations not allowed", memPkg.Path)) + } // make and set package if doesn't exist. pn := (*PackageNode)(nil) pv := (*PackageValue)(nil) @@ -237,6 +262,56 @@ func (m *Machine) RunMemPackage(memPkg *std.MemPackage, save bool) (*PackageNode return pn, pv } +// checkDuplicates returns true if there duplicate declarations in the fset. +func checkDuplicates(fset *FileSet) bool { + defined := make(map[Name]struct{}, 128) + for _, f := range fset.Files { + for _, d := range f.Decls { + var name Name + switch d := d.(type) { + case *FuncDecl: + if d.Name == "init" { //nolint:goconst + continue + } + name = d.Name + if d.IsMethod { + name = Name(destar(d.Recv.Type).String()) + "." + name + } + case *TypeDecl: + name = d.Name + case *ValueDecl: + for _, nx := range d.NameExprs { + if nx.Name == "_" { + continue + } + if _, ok := defined[nx.Name]; ok { + return true + } + defined[nx.Name] = struct{}{} + } + continue + default: + continue + } + if name == "_" { + continue + } + if _, ok := defined[name]; ok { + return true + } + defined[name] = struct{}{} + } + } + return false +} + +func destar(x Expr) Expr { + if x, ok := x.(*StarExpr); ok { + return x.X + } + return x +} + // Tests all test files in a mempackage. // Assumes that the importing of packages is handled elsewhere. // The resulting package value and node become injected with TestMethods and @@ -500,7 +575,7 @@ func (m *Machine) runFiles(fns ...*FileNode) { "loop in variable initialization: dependency trail %v circularly depends on %s", loopfindr, dep)) } } - // run dependecy declaration + // run dependency declaration loopfindr = append(loopfindr, dep) runDeclarationFor(fn, *depdecl) loopfindr = loopfindr[:len(loopfindr)-1] diff --git a/gnovm/pkg/gnolang/machine_test.go b/gnovm/pkg/gnolang/machine_test.go index e37fc508cb6..4268e3f3332 100644 --- a/gnovm/pkg/gnolang/machine_test.go +++ b/gnovm/pkg/gnolang/machine_test.go @@ -1,6 +1,16 @@ package gnolang -import "testing" +import ( + "fmt" + "testing" + + dbm "github.com/gnolang/gno/tm2/pkg/db" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/store/dbadapter" + "github.com/gnolang/gno/tm2/pkg/store/iavl" + stypes "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/jaekwon/testify/assert" +) func BenchmarkCreateNewMachine(b *testing.B) { for i := 0; i < b.N; i++ { @@ -8,3 +18,41 @@ func BenchmarkCreateNewMachine(b *testing.B) { m.Release() } } + +func TestRunMemPackageWithOverrides_revertToOld(t *testing.T) { + // A test to check revertToOld is correctly putting back an old value, + // after preprocessing fails. + db := dbm.NewMemDB() + baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{}) + iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) + store := NewStore(nil, baseStore, iavlStore) + m := NewMachine("std", store) + m.RunMemPackageWithOverrides(&std.MemPackage{ + Name: "std", + Path: "std", + Files: []*std.MemFile{ + {Name: "a.gno", Body: `package std; func Redecl(x int) string { return "1" }`}, + }, + }, true) + result := func() (p string) { + defer func() { + p = fmt.Sprint(recover()) + }() + m.RunMemPackageWithOverrides(&std.MemPackage{ + Name: "std", + Path: "std", + Files: []*std.MemFile{ + {Name: "b.gno", Body: `package std; func Redecl(x int) string { var y string; _, _ = y; return "2" }`}, + }, + }, true) + return + }() + t.Log("panic trying to redeclare invalid func", result) + m.RunStatement(S(Call(X("Redecl"), 11))) + + // Check last value, assuming it is the result of Redecl. + v := m.Values[0] + assert.NotNil(t, v) + assert.Equal(t, v.T.Kind(), StringKind) + assert.Equal(t, v.V, StringValue("1")) +} diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 2a9e0b51a97..b127cd32421 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1092,13 +1092,21 @@ func PackageNameFromFileBody(name, body string) Name { return Name(astFile.Name.Name) } -// NOTE: panics if package name is invalid. +// ReadMemPackage initializes a new MemPackage by reading the OS directory +// at dir, and saving it with the given pkgPath (import path). +// The resulting MemPackage will contain the names and content of all *.gno files, +// and additionally README.md, LICENSE, and gno.mod. +// +// ReadMemPackage does not perform validation aside from the package's name; +// the files are not parsed but their contents are merely stored inside a MemFile. +// +// NOTE: panics if package name is invalid (characters must be alphanumeric or _, +// lowercase, and must start with a letter). func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { files, err := os.ReadDir(dir) if err != nil { panic(err) } - memPkg := &std.MemPackage{Path: pkgPath} allowedFiles := []string{ // make case insensitive? "gno.mod", "LICENSE", @@ -1107,27 +1115,43 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { allowedFileExtensions := []string{ ".gno", } - var pkgName Name + list := make([]string, 0, len(files)) for _, file := range files { if file.IsDir() || strings.HasPrefix(file.Name(), ".") || (!endsWith(file.Name(), allowedFileExtensions) && !contains(allowedFiles, file.Name())) { continue } - fpath := filepath.Join(dir, file.Name()) + list = append(list, filepath.Join(dir, file.Name())) + } + return ReadMemPackageFromList(list, pkgPath) +} + +// ReadMemPackageFromList creates a new [std.MemPackage] with the specified pkgPath, +// containing the contents of all the files provided in the list slice. +// No parsing or validation is done on the filenames. +// +// NOTE: panics if package name is invalid (characters must be alphanumeric or _, +// lowercase, and must start with a letter). +func ReadMemPackageFromList(list []string, pkgPath string) *std.MemPackage { + memPkg := &std.MemPackage{Path: pkgPath} + var pkgName Name + for _, fpath := range list { + fname := filepath.Base(fpath) bz, err := os.ReadFile(fpath) if err != nil { panic(err) } - if pkgName == "" && strings.HasSuffix(file.Name(), ".gno") { - pkgName = PackageNameFromFileBody(file.Name(), string(bz)) + // XXX: should check that all pkg names are the same (else package is invalid) + if pkgName == "" && strings.HasSuffix(fname, ".gno") { + pkgName = PackageNameFromFileBody(fname, string(bz)) if strings.HasSuffix(string(pkgName), "_test") { pkgName = pkgName[:len(pkgName)-len("_test")] } } memPkg.Files = append(memPkg.Files, &std.MemFile{ - Name: file.Name(), + Name: fname, Body: string(bz), }) } @@ -1141,33 +1165,29 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { return memPkg } -func PrecompileMemPackage(memPkg *std.MemPackage) error { - return nil -} - -// Returns the code fileset minus any spurious or test files. +// ParseMemPackage executes [ParseFile] on each file of the memPkg, excluding +// test and spurious (non-gno) files. The resulting *FileSet is returned. +// +// If one of the files has a different package name than memPkg.Name, +// or [ParseFile] returns an error, ParseMemPackage panics. func ParseMemPackage(memPkg *std.MemPackage) (fset *FileSet) { fset = &FileSet{} for _, mfile := range memPkg.Files { - if !strings.HasSuffix(mfile.Name, ".gno") { - continue // skip spurious file. + if !strings.HasSuffix(mfile.Name, ".gno") || + endsWith(mfile.Name, []string{"_test.gno", "_filetest.gno"}) { + continue // skip spurious or test file. } n, err := ParseFile(mfile.Name, mfile.Body) if err != nil { panic(errors.Wrap(err, "parsing file "+mfile.Name)) } - if strings.HasSuffix(mfile.Name, "_test.gno") { - // skip test file. - } else if strings.HasSuffix(mfile.Name, "_filetest.gno") { - // skip test file. - } else if memPkg.Name == string(n.PkgName) { - // add package file. - fset.AddFiles(n) - } else { + if memPkg.Name != string(n.PkgName) { panic(fmt.Sprintf( "expected package name [%s] but got [%s]", memPkg.Name, n.PkgName)) } + // add package file. + fset.AddFiles(n) } return fset } @@ -1236,7 +1256,11 @@ func (fs *FileSet) GetDeclFor(n Name) (*FileNode, *Decl) { func (fs *FileSet) GetDeclForSafe(n Name) (*FileNode, *Decl, bool) { // XXX index to bound to linear time. - for _, fn := range fs.Files { + + // Iteration happens reversing fs.Files; this is because the LAST declaration + // of n is what we are looking for. + for i := len(fs.Files) - 1; i >= 0; i-- { + fn := fs.Files[i] for i, dn := range fn.Decls { if _, isImport := dn.(*ImportDecl); isImport { // imports in other files don't count. @@ -1377,6 +1401,7 @@ func (x *PackageNode) DefineNative(n Name, ps, rs FieldTypeExprs, native func(*M if native == nil { panic("DefineNative expects a function, but got nil") } + fd := FuncD(n, ps, rs, nil) fd = Preprocess(nil, x, fd).(*FuncDecl) ft := evalStaticType(nil, x, &fd.Type).(*FuncType) @@ -1457,6 +1482,22 @@ type StaticBlock struct { Consts []Name // TODO consider merging with Names. Externs []Name Loc Location + + // temporary storage for rolling back redefinitions. + oldValues []oldValue +} + +type oldValue struct { + idx uint16 + value Value +} + +// revert values upon failure of redefinitions. +func (sb *StaticBlock) revertToOld() { + for _, ov := range sb.oldValues { + sb.Block.Values[ov.idx].V = ov.value + } + sb.oldValues = nil } // Implements BlockNode @@ -1656,7 +1697,6 @@ func (sb *StaticBlock) GetStaticTypeOfAt(store Store, path ValuePath) Type { path.Depth -= 1 } } - panic("should not happen") } // Implements BlockNode. @@ -1706,12 +1746,11 @@ func (sb *StaticBlock) GetValueRef(store Store, n Name) *TypedValue { // values, which are pre-computeed in the preprocessor. // Once a typed value is defined, it cannot be changed. // -// NOTE: Currently tv.V is only set when the value -// represents a Type(Value). The purpose of tv is to describe -// the invariant of a named value, at the minimum its type, -// but also sometimes the typeval value; but we could go -// further and store preprocessed constant results here -// too. See "anyValue()" and "asValue()" for usage. +// NOTE: Currently tv.V is only set when the value represents a Type(Value) or +// a FuncValue. The purpose of tv is to describe the invariant of a named +// value, at the minimum its type, but also sometimes the typeval value; but we +// could go further and store preprocessed constant results here too. See +// "anyValue()" and "asValue()" for usage. func (sb *StaticBlock) Define(n Name, tv TypedValue) { sb.Define2(false, n, tv.T, tv) } @@ -1754,16 +1793,32 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { } old := sb.Block.Values[idx] if !old.IsUndefined() { - if tv.T.TypeID() != old.T.TypeID() { - panic(fmt.Sprintf( - "StaticBlock.Define2(%s) cannot change .T; was %v, new %v", - n, old.T, tv.T)) - } - if tv.V != old.V { - panic(fmt.Sprintf( - "StaticBlock.Define2(%s) cannot change .V", - n)) + if tv.T.Kind() == FuncKind && tv.T.(*FuncType).IsZero() { + // special case, + // allow re-predefining for func upgrades. + // keep the old type so we can check it at preprocessor. + // fmt.Println("QWEQWEQWE>>>", old.String()) + // fmt.Println("QWEQWEQWE>>>", tv.String()) + tv.T = old.T + fv := tv.V.(*FuncValue) + fv.Type = old.T + st = old.T + sb.oldValues = append(sb.oldValues, + oldValue{idx, old.V}) + } else { + if tv.T.TypeID() != old.T.TypeID() { + panic(fmt.Sprintf( + "StaticBlock.Define2(%s) cannot change .T; was %v, new %v", + n, old.T, tv.T)) + } + if tv.V != old.V { + panic(fmt.Sprintf( + "StaticBlock.Define2(%s) cannot change .V", + n)) + } } + // Allow re-definitions if they have the same type. + // (In normal scenarios, duplicate declarations are "caught" by RunMemPackage.) } sb.Block.Values[idx] = tv sb.Types[idx] = st @@ -2024,6 +2079,7 @@ const ( ) // TODO: consider length restrictions. +// If this function is changed, ReadMemPackage's documentation should be updated accordingly. func validatePkgName(name string) { if nameOK, _ := regexp.MatchString( `^[a-z][a-z0-9_]+$`, name); !nameOK { diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index ddab8302d2d..9162a710d7d 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -4,7 +4,7 @@ import ( "fmt" "math/big" - "github.com/cockroachdb/apd" + "github.com/cockroachdb/apd/v3" ) // ---------------------------------------- diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index 8d652667111..c7274dd73af 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -58,6 +58,13 @@ func (m *Machine) doOpCall() { clo := fr.Func.GetClosure(m.Store) b := m.Alloc.NewBlock(fr.Func.GetSource(m.Store), clo) m.PushBlock(b) + if fv.nativeBody == nil && fv.NativePkg != "" { + // native function, unmarshaled so doesn't have nativeBody yet + fv.nativeBody = m.Store.GetNative(fv.NativePkg, fv.NativeName) + if fv.nativeBody == nil { + panic(fmt.Sprintf("natively defined function (%q).%s could not be resolved", fv.NativePkg, fv.NativeName)) + } + } if fv.nativeBody == nil { fbody := fv.GetBodyFromSource(m.Store) if len(ft.Results) == 0 { @@ -155,7 +162,7 @@ func (m *Machine) doOpCall() { // TODO: some more pt <> pv.Type // reconciliations/conversions necessary. - // Make a copy so that a reference to the arguemnt isn't used + // Make a copy so that a reference to the argument 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/op_eval.go b/gnovm/pkg/gnolang/op_eval.go index 83dd3737b6b..283d035dca2 100644 --- a/gnovm/pkg/gnolang/op_eval.go +++ b/gnovm/pkg/gnolang/op_eval.go @@ -9,13 +9,13 @@ import ( "strconv" "strings" - "github.com/cockroachdb/apd" + "github.com/cockroachdb/apd/v3" ) func (m *Machine) doOpEval() { x := m.PeekExpr(1) if debug { - debug.Printf("EVAL: %v\n", x) + debug.Printf("EVAL: (%T) %v\n", x, x) // fmt.Println(m.String()) } // This case moved out of switch for performance. @@ -47,8 +47,8 @@ func (m *Machine) doOpEval() { bi := big.NewInt(0) // TODO optimize. // TODO deal with base. - if len(x.Value) > 2 && x.Value[0] == '0' { - var ok bool = false + var ok bool + if len(x.Value) >= 2 && x.Value[0] == '0' { switch x.Value[1] { case 'b', 'B': _, ok = bi.SetString(x.Value[2:], 2) @@ -56,6 +56,8 @@ func (m *Machine) doOpEval() { _, ok = bi.SetString(x.Value[2:], 8) case 'x', 'X': _, ok = bi.SetString(x.Value[2:], 16) + case '0', '1', '2', '3', '4', '5', '6', '7': + _, ok = bi.SetString(x.Value, 8) default: ok = false } @@ -160,8 +162,7 @@ func (m *Machine) doOpEval() { panic(fmt.Sprintf("error computing exponent: %v", err)) } // Step 3 make Decimal from mantissa and exp. - var dValue *big.Int - dValue = new(big.Int) + dValue := new(apd.BigInt) _, ok := dValue.SetString(hexString, 16) if !ok { panic(fmt.Sprintf("can't convert %s to decimal", value)) diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index b46e343b00e..b3bf240aea1 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -78,8 +78,12 @@ func (m *Machine) doOpIndex2() { func (m *Machine) doOpSelector() { sx := m.PopExpr().(*SelectorExpr) xv := m.PeekValue(1) - res := xv.GetPointerTo(m.Alloc, m.Store, sx.Path) - *xv = res.Deref() // reuse as result + res := xv.GetPointerTo(m.Alloc, m.Store, sx.Path).Deref() + if debug { + m.Printf("-v[S] %v\n", xv) + m.Printf("+v[S] %v\n", res) + } + *xv = res // reuse as result } func (m *Machine) doOpSlice() { diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index 70886497d5e..84c39716eec 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -4,7 +4,7 @@ import ( "fmt" "math/big" - "github.com/cockroachdb/apd" + "github.com/cockroachdb/apd/v3" ) func (m *Machine) doOpInc() { diff --git a/gnovm/pkg/gnolang/op_unary.go b/gnovm/pkg/gnolang/op_unary.go index 331bea63ff9..9c330c7f8f1 100644 --- a/gnovm/pkg/gnolang/op_unary.go +++ b/gnovm/pkg/gnolang/op_unary.go @@ -4,7 +4,7 @@ import ( "fmt" "math/big" - "github.com/cockroachdb/apd" + "github.com/cockroachdb/apd/v3" ) func (m *Machine) doOpUpos() { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 1a1edc41222..a4576fa6f48 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -755,6 +755,13 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { resn := Preprocess(store, last, n2) return resn, TRANS_CONTINUE } + + // Left and right hand expressions must evaluate to a boolean typed value if + // the operation is a logical AND or OR. + if (n.Op == LAND || n.Op == LOR) && (lt.Kind() != BoolKind || rt.Kind() != BoolKind) { + panic("operands of boolean operators must evaluate to boolean typed values") + } + // General case. lcx, lic := n.Left.(*ConstExpr) rcx, ric := n.Right.(*ConstExpr) @@ -1817,8 +1824,13 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { for i, vx := range n.Values { if cx, ok := vx.(*ConstExpr); ok && !cx.TypedValue.IsUndefined() { - // if value is non-nil const expr: - tvs[i] = cx.TypedValue + if n.Const { + // const _ = : static block should contain value + tvs[i] = cx.TypedValue + } else { + // var _ = : static block should NOT contain value + tvs[i] = anyValue(cx.TypedValue.T) + } } else { // for var decls of non-const expr. st := sts[i] @@ -2909,7 +2921,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De } else { dt = rt.(*DeclaredType) } - dt.DefineMethod(&FuncValue{ + if !dt.TryDefineMethod(&FuncValue{ Type: ft, IsMethod: true, Source: cd, @@ -2919,16 +2931,34 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De PkgPath: pkg.PkgPath, body: cd.Body, nativeBody: nil, - }) + }) { + // Revert to old function declarations in the package we're preprocessing. + pkg := packageOf(last) + pkg.StaticBlock.revertToOld() + panic(fmt.Sprintf("redeclaration of method %s.%s", + dt.Name, cd.Name)) + } } else { ftv := pkg.GetValueRef(store, cd.Name) ft := ftv.T.(*FuncType) cd.Type = *Preprocess(store, last, &cd.Type).(*FuncTypeExpr) ft2 := evalStaticType(store, last, &cd.Type).(*FuncType) - *ft = *ft2 + if !ft.IsZero() { + // redefining function. + // make sure the type is the same. + if ft.TypeID() != ft2.TypeID() { + panic(fmt.Sprintf( + "Redefinition (%s) cannot change .T; was %v, new %v", + cd, ft, ft2)) + } + // keep the orig type. + } else { + *ft = *ft2 + } // XXX replace attr w/ ft? // return Preprocess(store, last, cd).(Decl), true } + // Full type declaration/preprocessing already done in tryPredefine return d, false case *ValueDecl: return Preprocess(store, last, cd).(Decl), true @@ -3122,19 +3152,30 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } // define a FuncValue w/ above type as d.Name. // fill in later during *FuncDecl:BLOCK. + fv := &FuncValue{ + Type: ft, + IsMethod: false, + Source: d, + Name: d.Name, + Closure: nil, // set lazily. + FileName: fileNameOf(last), + PkgPath: pkg.PkgPath, + body: d.Body, + nativeBody: nil, + } + // NOTE: fv.body == nil means no body (ie. not even curly braces) + // len(fv.body) == 0 could mean also {} (ie. no statements inside) + if fv.body == nil && store != nil { + fv.nativeBody = store.GetNative(pkg.PkgPath, d.Name) + if fv.nativeBody == nil { + panic(fmt.Sprintf("function %s does not have a body but is not natively defined", d.Name)) + } + fv.NativePkg = pkg.PkgPath + fv.NativeName = d.Name + } pkg.Define(d.Name, TypedValue{ T: ft, - V: &FuncValue{ - Type: ft, - IsMethod: false, - Source: d, - Name: d.Name, - Closure: nil, // set lazily. - FileName: fileNameOf(last), - PkgPath: pkg.PkgPath, - body: d.Body, - nativeBody: nil, - }, + V: fv, }) if d.Name == "init" { // init functions can't be referenced. diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 567aea58284..519b183ad3a 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -151,6 +151,7 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { // Updates to .newCreated/.newEscaped /.newDeleted made here. (first gen) // More appends happen during FinalizeRealmTransactions(). (second+ gen) rlm.MarkDirty(po) + if co != nil { co.IncRefCount() if co.GetRefCount() > 1 { @@ -166,6 +167,7 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { rlm.MarkNewReal(co) } } + if xo != nil { xo.DecRefCount() if xo.GetRefCount() == 0 { @@ -1129,18 +1131,23 @@ func copyValueWithRefs(parent Object, val Value) Value { if cv.Closure != nil { closure = toRefValue(parent, cv.Closure) } - if cv.nativeBody != nil { + // nativeBody funcs which don't come from NativeStore (and thus don't + // have NativePkg/Name) can't be persisted, and should not be able + // to get here anyway. + if cv.nativeBody != nil && cv.NativePkg == "" { panic("should not happen") } ft := copyTypeWithRefs(cv.Type) return &FuncValue{ - Type: ft, - IsMethod: cv.IsMethod, - Source: source, - Name: cv.Name, - Closure: closure, - FileName: cv.FileName, - PkgPath: cv.PkgPath, + Type: ft, + IsMethod: cv.IsMethod, + Source: source, + Name: cv.Name, + Closure: closure, + FileName: cv.FileName, + PkgPath: cv.PkgPath, + NativePkg: cv.NativePkg, + NativeName: cv.NativeName, } case *BoundMethodValue: fnc := copyValueWithRefs(cv, cv.Func).(*FuncValue) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 24aff4936f3..be4a854e7ce 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -17,6 +17,9 @@ type PackageGetter func(pkgPath string) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) type PackageInjector func(store Store, pn *PackageNode) +// NativeStore is a function which can retrieve native bodies of native functions. +type NativeStore func(pkgName string, name Name) func(m *Machine) + type Store interface { // STABLE SetPackageGetter(PackageGetter) @@ -48,10 +51,12 @@ type Store interface { GetMemPackage(path string) *std.MemPackage GetMemFile(path string, name string) *std.MemFile IterMemPackage() <-chan *std.MemPackage - ClearObjectCache() // for each delivertx. - Fork() Store // for checktx, simulate, and queries. - SwapStores(baseStore, iavlStore store.Store) // for gas wrappers. - SetPackageInjector(PackageInjector) // for natives + ClearObjectCache() // for each delivertx. + Fork() Store // for checktx, simulate, and queries. + SwapStores(baseStore, iavlStore store.Store) // for gas wrappers. + SetPackageInjector(PackageInjector) // for natives + SetNativeStore(NativeStore) // for "new" natives XXX + GetNative(pkgPath string, name Name) func(m *Machine) // for "new" natives XXX SetLogStoreOps(enabled bool) SprintStoreOps() string LogSwitchRealm(rlmpath string) // to mark change of realm boundaries @@ -70,6 +75,7 @@ type defaultStore struct { baseStore store.Store // for objects, types, nodes iavlStore store.Store // for escaped object hashes pkgInjector PackageInjector // for injecting natives + nativeStore NativeStore // for injecting natives go2gnoMap map[string]string // go pkgpath.name -> gno pkgpath.name go2gnoStrict bool // if true, native->gno type conversion must be registered. @@ -601,6 +607,7 @@ func (ds *defaultStore) Fork() Store { baseStore: ds.baseStore, iavlStore: ds.iavlStore, pkgInjector: ds.pkgInjector, + nativeStore: ds.nativeStore, go2gnoMap: ds.go2gnoMap, go2gnoStrict: ds.go2gnoStrict, opslog: nil, // new ops log. @@ -620,6 +627,17 @@ func (ds *defaultStore) SetPackageInjector(inj PackageInjector) { ds.pkgInjector = inj } +func (ds *defaultStore) SetNativeStore(ns NativeStore) { + ds.nativeStore = ns +} + +func (ds *defaultStore) GetNative(pkgPath string, name Name) func(m *Machine) { + if ds.nativeStore != nil { + return ds.nativeStore(pkgPath, name) + } + return nil +} + func (ds *defaultStore) Flush() { // XXX } diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 6aed71fcf9b..34565b7a1b6 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1084,6 +1084,12 @@ type FuncType struct { bound *FuncType } +// true for predefined func types that are not filled in yet. +func (ft *FuncType) IsZero() bool { + // XXX be explicit. + return ft.Params == nil && ft.Results == nil && ft.typeid.IsZero() && ft.bound == nil +} + // if ft is a method, returns whether method takes a pointer receiver. func (ft *FuncType) HasPointerReceiver() bool { if debug { @@ -1445,10 +1451,61 @@ func (dt *DeclaredType) GetPkgPath() string { } func (dt *DeclaredType) DefineMethod(fv *FuncValue) { + if !dt.TryDefineMethod(fv) { + panic(fmt.Sprintf("redeclaration of method %s.%s", + dt.Name, fv.Name)) + } +} + +// TryDefineMethod attempts to define the method fv on type dt. +// It returns false if this does not succeeds, as a result of a re-declaration. +func (dt *DeclaredType) TryDefineMethod(fv *FuncValue) bool { + name := fv.Name + + // Handle redeclarations. + for i, tv := range dt.Methods { + ofv := tv.V.(*FuncValue) + if ofv.Name != name { + continue + } + + // Do not allow redeclaring (override) a method. + // In the future we may allow this, just like we + // allow package-level function overrides. + + // Special case: if the type and location are the same, + // ignore and do not redefine. + // This is due to PreprocessAllFilesAndSaveBlocknodes, + // and because the preprocessor fills some of the + // method's FuncValue. Since the method was already + // filled in prior to PreprocessAllFilesAndSaveBlocks, + // there is no need to re-set it. + // Keep this or move this check outside. + if fv.Type.TypeID() == ofv.Type.TypeID() && + fv.Source.GetLocation() == ofv.Source.GetLocation() { + return true + } + + // Special case: allow defining a native body. + if fv.Type.TypeID() == ofv.Type.TypeID() && + !ofv.IsNative() && fv.IsNative() { + dt.Methods[i] = TypedValue{ + T: fv.Type, // keep old type. + V: fv, + } + return true + } + + // Otherwise fail and return false. + return false + } + + // If not redeclaring, just append. dt.Methods = append(dt.Methods, TypedValue{ T: fv.Type, V: fv, }) + return true } func (dt *DeclaredType) GetPathForName(n Name) ValuePath { diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 57f8f6d393d..041f1557e61 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -210,9 +210,9 @@ func UverseNode() *PackageNode { // append(nil, *SliceValue) new list --------- list := make([]TypedValue, argsl) if 0 < argsl { - copy( - list[:argsl], - argsb.List[argso:argso+argsl]) + for i := 0; i < argsl; i++ { + list[i] = argsb.List[argso+i].unrefCopy(m.Alloc, m.Store) + } } m.PushValue(TypedValue{ T: xt, @@ -294,14 +294,25 @@ func UverseNode() *PackageNode { // append(*SliceValue.List, *SliceValue) --------- list := xvb.List if argsb.Data == nil { - copy( - list[xvo+xvl:xvo+xvl+argsl], - argsb.List[argso:argso+argsl]) + for i := 0; i < argsl; i++ { + oldElem := list[xvo+xvl+i] + // unrefCopy will resolve references and copy their values + // to copy by value rather than by reference. + newElem := argsb.List[argso+i].unrefCopy(m.Alloc, m.Store) + list[xvo+xvl+i] = newElem + + m.Realm.DidUpdate( + xvb, + oldElem.GetFirstObject(m.Store), + newElem.GetFirstObject(m.Store), + ) + } } else { copyDataToList( list[xvo+xvl:xvo+xvl+argsl], argsb.Data[argso:argso+argsl], xt.Elem()) + m.Realm.DidUpdate(xvb, nil, nil) } } else { // append(*SliceValue.Data, *SliceValue) --------- @@ -310,6 +321,7 @@ func UverseNode() *PackageNode { copyListToData( data[xvo+xvl:xvo+xvl+argsl], argsb.List[argso:argso+argsl]) + m.Realm.DidUpdate(xvb, nil, nil) } else { copy( data[xvo+xvl:xvo+xvl+argsl], @@ -363,9 +375,9 @@ func UverseNode() *PackageNode { list := make([]TypedValue, xvl+argsl) if 0 < xvl { if xvb.Data == nil { - copy( - list[:xvl], - xvb.List[xvo:xvo+xvl]) + for i := 0; i < xvl; i++ { + list[i] = xvb.List[xvo+i].unrefCopy(m.Alloc, m.Store) + } } else { panic("should not happen") /* @@ -379,9 +391,9 @@ func UverseNode() *PackageNode { } if 0 < argsl { if argsb.Data == nil { - copy( - list[xvl:xvl+argsl], - argsb.List[argso:argso+argsl]) + for i := 0; i < argsl; i++ { + list[xvl+i] = argsb.List[argso+i].unrefCopy(m.Alloc, m.Store) + } } else { copyDataToList( list[xvl:xvl+argsl], @@ -457,11 +469,12 @@ func UverseNode() *PackageNode { return } else { // append(*SliceValue, *NativeValue) new list -------- - list := make([]TypedValue, xvl+argsl) + listLen := xvl + argsl + list := make([]TypedValue, listLen) if 0 < xvl { - copy( - list[:xvl], - xvb.List[xvo:xvo+xvl]) + for i := 0; i < listLen; i++ { + list[i] = xvb.List[xvo+i].unrefCopy(m.Alloc, m.Store) + } } if 0 < argsl { copyNativeToList( diff --git a/gnovm/pkg/gnolang/uverse_test.go b/gnovm/pkg/gnolang/uverse_test.go new file mode 100644 index 00000000000..7280d131ec5 --- /dev/null +++ b/gnovm/pkg/gnolang/uverse_test.go @@ -0,0 +1,160 @@ +package gnolang + +import ( + "testing" +) + +type printlnTestCases struct { + name string + code string + expected string +} + +func TestIssue1337PrintNilSliceAsUndefined(t *testing.T) { + test := []printlnTestCases{ + { + name: "print empty slice", + code: `package test + func main() { + emptySlice1 := make([]int, 0) + emptySlice2 := []int{} + + println(emptySlice1) + println(emptySlice2) + }`, + expected: "slice[]\nslice[]\n", + }, + { + name: "nil slice", + code: `package test + func main() { + println(nil) + }`, + expected: "undefined\n", + }, + { + name: "print empty string slice", + code: `package test + func main() { + var a []string + println(a) + }`, + expected: "nil []string\n", + }, + { + name: "print non-empty slice", + code: `package test + func main() { + a := []string{"a", "b"} + println(a) + }`, + expected: "slice[(\"a\" string),(\"b\" string)]\n", + }, + { + name: "print empty map", + code: `package test + func main() { + var a map[string]string + println(a) + }`, + expected: "nil map[string]string\n", + }, + { + name: "print non-empty map", + code: `package test + func main() { + a := map[string]string{"a": "b"} + println(a) + }`, + expected: "map{(\"a\" string):(\"b\" string)}\n", + }, + { + name: "print nil struct", + code: `package test + func main() { + var a struct{} + println(a) + }`, + expected: "struct{}\n", + }, + { + name: "print function", + code: `package test + func foo(a, b int) int { + return a + b + } + func main() { + println(foo(1, 3)) + }`, + expected: "4\n", + }, + { + name: "print composite slice", + code: `package test + func main() { + a, b, c, d := 1, 2, 3, 4 + x := []int{ + a: b, + c: d, + } + println(x) + }`, + expected: "slice[(0 int),(2 int),(0 int),(4 int)]\n", + }, + { + name: "simple recover case", + code: `package test + + func main() { + defer func() { println("recover", recover()) }() + println("simple panic") + }`, + expected: "simple panic\nrecover undefined\n", + }, + { + name: "nested recover", + code: `package test + + func main() { + defer func() { println("outer recover", recover()) }() + defer func() { println("nested panic") }() + println("simple panic") + }`, + expected: "simple panic\nnested panic\nouter recover undefined\n", + }, + { + name: "print non-nil function", + code: `package test + func f() int { + return 1 + } + + func main() { + g := f + println(g) + }`, + expected: "f\n", + }, + { + name: "print primitive types", + code: `package test + func main() { + println(1) + println(1.1) + println(true) + println("hello") + }`, + expected: "1\n1.1\ntrue\nhello\n", + }, + } + + for _, tc := range test { + t.Run(tc.name, func(t *testing.T) { + m := NewMachine("test", nil) + n := MustParseFile("main.go", tc.code) + m.RunFiles(n) + m.RunMain() + assertOutput(t, tc.code, tc.expected) + }) + } +} diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 3bdd3332e08..53d482613a1 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -10,7 +10,7 @@ import ( "strings" "unsafe" - "github.com/cockroachdb/apd" + "github.com/cockroachdb/apd/v3" "github.com/gnolang/gno/tm2/pkg/crypto" ) @@ -43,7 +43,8 @@ func (*Block) assertValue() {} func (RefValue) assertValue() {} const ( - nilStr = "nil" + nilStr = "nil" + undefinedStr = "undefined" ) var ( @@ -526,18 +527,32 @@ func (sv *StructValue) Copy(alloc *Allocator) *StructValue { // makes construction TypedValue{T:*FuncType{},V:*FuncValue{}} // faster. type FuncValue struct { - Type Type // includes unbound receiver(s) - IsMethod bool // is an (unbound) method - Source BlockNode // for block mem allocation - Name Name // name of function/method - Closure Value // *Block or RefValue to closure (may be nil for file blocks; lazy) - FileName Name // file name where declared - PkgPath string + Type Type // includes unbound receiver(s) + IsMethod bool // is an (unbound) method + Source BlockNode // for block mem allocation + Name Name // name of function/method + Closure Value // *Block or RefValue to closure (may be nil for file blocks; lazy) + FileName Name // file name where declared + PkgPath string + NativePkg string // for native bindings through NativeStore + NativeName Name // not redundant with Name; this cannot be changed in userspace body []Stmt // function body nativeBody func(*Machine) // alternative to Body } +func (fv *FuncValue) IsNative() bool { + if fv.NativePkg == "" && fv.NativeName == "" { + return false + } + if fv.NativePkg == "" || fv.NativeName == "" { + panic(fmt.Sprintf("function (%q).%s has invalid native pkg/name ((%q).%s)", + fv.Source.GetLocation().PkgPath, fv.Name, + fv.NativePkg, fv.NativeName)) + } + return true +} + func (fv *FuncValue) Copy(alloc *Allocator) *FuncValue { alloc.AllocateFunc() return &FuncValue{ @@ -548,6 +563,8 @@ func (fv *FuncValue) Copy(alloc *Allocator) *FuncValue { Closure: fv.Closure, FileName: fv.FileName, PkgPath: fv.PkgPath, + NativePkg: fv.NativePkg, + NativeName: fv.NativeName, body: fv.body, nativeBody: fv.nativeBody, } @@ -1010,6 +1027,28 @@ func (tv TypedValue) Copy(alloc *Allocator) (cp TypedValue) { return } +// unrefCopy makes a copy of the underlying value in the case of reference values. +// It copies other values as expected using the normal Copy method. +func (tv TypedValue) unrefCopy(alloc *Allocator, store Store) (cp TypedValue) { + switch tv.V.(type) { + case RefValue: + cp.T = tv.T + refObject := tv.GetFirstObject(store) + switch refObjectValue := refObject.(type) { + case *ArrayValue: + cp.V = refObjectValue.Copy(alloc) + case *StructValue: + cp.V = refObjectValue.Copy(alloc) + default: + cp = tv + } + default: + cp = tv.Copy(alloc) + } + + return +} + // Returns encoded bytes for primitive values. // These bytes are used for both value hashes as well // as hash key bytes. @@ -2320,12 +2359,8 @@ func (b *Block) GetPointerTo(store Store, path ValuePath) PointerValue { // the generation for uverse is 0. If path.Depth is // 0, it implies that b == uverse, and the condition // would fail as if it were 1. - i := uint8(1) -LOOP: - if i < path.Depth { + for i := uint8(1); i < path.Depth; i++ { b = b.GetParent(store) - i++ - goto LOOP } return b.GetPointerToInt(store, int(path.Index)) } diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index cc7c0de9f09..e2000defdde 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -7,7 +7,7 @@ import ( "reflect" "strconv" - "github.com/cockroachdb/apd" + "github.com/cockroachdb/apd/v3" ) // t cannot be nil or untyped or DataByteType. @@ -1113,7 +1113,7 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { return // done case BigdecKind: dst.T = t - dst.V = BigdecValue{V: apd.NewWithBigInt(bi, 0)} + dst.V = BigdecValue{V: apd.NewWithBigInt(new(apd.BigInt).SetMathBigInt(bi), 0)} return // done default: panic(fmt.Sprintf( diff --git a/gnovm/pkg/gnolang/values_conversions_test.go b/gnovm/pkg/gnolang/values_conversions_test.go index e92496c1ac2..5436347733f 100644 --- a/gnovm/pkg/gnolang/values_conversions_test.go +++ b/gnovm/pkg/gnolang/values_conversions_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "github.com/cockroachdb/apd" + "github.com/cockroachdb/apd/v3" "github.com/stretchr/testify/require" ) diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index f9a0128d7f9..34187e32879 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -7,6 +7,47 @@ import ( "strings" ) +type protectedStringer interface { + ProtectedString(*seenValues) string +} + +// This indicates the maximum ancticipated depth of the stack when printing a Value type. +const defaultSeenValuesSize = 32 + +type seenValues struct { + values []Value +} + +func (sv *seenValues) Put(v Value) { + sv.values = append(sv.values, v) +} + +func (sv *seenValues) Contains(v Value) bool { + for _, vv := range sv.values { + if vv == v { + return true + } + } + + return false +} + +// Pop should be called by using a defer after each Put. +// Consider why this is necessary: +// - we are printing an array of structs +// - each invocation of struct.ProtectedString adds the value to the seenValues +// - without calling Pop before exiting struct.ProtectedString, the next call to +// struct.ProtectedString in the array.ProtectedString loop will not result in the value +// being printed if the value has already been print +// - this is NOT recursion and SHOULD be printed +func (sv *seenValues) Pop() { + sv.values = sv.values[:len(sv.values)-1] +} + +func newSeenValues() *seenValues { + return &seenValues{values: make([]Value, 0, defaultSeenValuesSize)} +} + func (v StringValue) String() string { return strconv.Quote(string(v)) } @@ -24,10 +65,21 @@ func (dbv DataByteValue) String() string { } func (av *ArrayValue) String() string { + return av.ProtectedString(newSeenValues()) +} + +func (av *ArrayValue) ProtectedString(seen *seenValues) string { + if seen.Contains(av) { + return fmt.Sprintf("%p", av) + } + + seen.Put(av) + defer seen.Pop() + ss := make([]string, len(av.List)) if av.Data == nil { for i, e := range av.List { - ss[i] = e.String() + ss[i] = e.ProtectedString(seen) } // NOTE: we may want to unify the representation, // but for now tests expect this to be different. @@ -41,17 +93,30 @@ func (av *ArrayValue) String() string { } func (sv *SliceValue) String() string { + return sv.ProtectedString(newSeenValues()) +} + +func (sv *SliceValue) ProtectedString(seen *seenValues) string { if sv.Base == nil { return "nil-slice" } + + if seen.Contains(sv) { + return fmt.Sprintf("%p", sv) + } + if ref, ok := sv.Base.(RefValue); ok { return fmt.Sprintf("slice[%v]", ref) } + + seen.Put(sv) + defer seen.Pop() + vbase := sv.Base.(*ArrayValue) if vbase.Data == nil { ss := make([]string, sv.Length) for i, e := range vbase.List[sv.Offset : sv.Offset+sv.Length] { - ss[i] = e.String() + ss[i] = e.ProtectedString(seen) } return "slice[" + strings.Join(ss, ",") + "]" } @@ -62,16 +127,40 @@ func (sv *SliceValue) String() string { } func (pv PointerValue) String() string { - // NOTE: cannot do below, due to recursion problems. - // TODO: create a different String2(...) function. - // return fmt.Sprintf("&%s", v.TypedValue.String()) - return fmt.Sprintf("&%p.(*%s)", pv.TV, pv.TV.T.String()) + return pv.ProtectedString(newSeenValues()) +} + +func (pv PointerValue) ProtectedString(seen *seenValues) string { + if seen.Contains(pv) { + return fmt.Sprintf("%p", &pv) + } + + seen.Put(pv) + defer seen.Pop() + + // Handle nil TV's, avoiding a nil pointer deref below. + if pv.TV == nil { + return "&" + } + + return fmt.Sprintf("&%s", pv.TV.ProtectedString(seen)) } func (sv *StructValue) String() string { + return sv.ProtectedString(newSeenValues()) +} + +func (sv *StructValue) ProtectedString(seen *seenValues) string { + if seen.Contains(sv) { + return fmt.Sprintf("%p", sv) + } + + seen.Put(sv) + defer seen.Pop() + ss := make([]string, len(sv.Fields)) for i, f := range sv.Fields { - ss[i] = f.String() + ss[i] = f.ProtectedString(seen) } return "struct{" + strings.Join(ss, ",") + "}" } @@ -104,9 +193,21 @@ func (v *BoundMethodValue) String() string { } func (mv *MapValue) String() string { + return mv.ProtectedString(newSeenValues()) +} + +func (mv *MapValue) ProtectedString(seen *seenValues) string { if mv.List == nil { return "zero-map" } + + if seen.Contains(mv) { + return fmt.Sprintf("%p", mv) + } + + seen.Put(mv) + defer seen.Pop() + ss := make([]string, 0, mv.GetLength()) next := mv.List.Head for next != nil { @@ -164,8 +265,9 @@ func (v RefValue) String() string { func (tv *TypedValue) Sprint(m *Machine) string { // if undefined, just "undefined". if tv == nil || tv.T == nil { - return "undefined" + return undefinedStr } + // if implements .String(), return it. if IsImplementedBy(gStringerType, tv.T) { res := m.Eval(Call(Sel(&ConstExpr{TypedValue: *tv}, "String"))) @@ -176,10 +278,28 @@ func (tv *TypedValue) Sprint(m *Machine) string { res := m.Eval(Call(Sel(&ConstExpr{TypedValue: *tv}, "Error"))) return res[0].GetString() } + + return tv.ProtectedSprint(newSeenValues(), true) +} + +func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType bool) string { + if seen.Contains(tv.V) { + return fmt.Sprintf("%p", tv) + } + // print declared type - if _, ok := tv.T.(*DeclaredType); ok { - return tv.String() + if _, ok := tv.T.(*DeclaredType); ok && considerDeclaredType { + return tv.ProtectedString(seen) } + + // This is a special case that became necessary after adding `ProtectedString()` methods to + // reliably prevent recursive print loops. + if tv.V != nil { + if v, ok := tv.V.(RefValue); ok { + return v.String() + } + } + // otherwise, default behavior. switch bt := baseOf(tv.T).(type) { case PrimitiveType: @@ -223,23 +343,13 @@ func (tv *TypedValue) Sprint(m *Machine) string { if tv.V == nil { return "invalid-pointer" } - return tv.V.(PointerValue).String() - case *ArrayType: - return tv.V.(*ArrayValue).String() - case *SliceType: - return tv.V.(*SliceValue).String() - case *StructType: - return tv.V.(*StructValue).String() - case *MapType: - return tv.V.(*MapValue).String() + return tv.V.(PointerValue).ProtectedString(seen) case *FuncType: switch fv := tv.V.(type) { case nil: ft := tv.T.String() - return "nil " + ft - case *FuncValue: - return fv.String() - case *BoundMethodValue: + return nilStr + " " + ft + case *FuncValue, *BoundMethodValue: return fv.String() default: panic(fmt.Sprintf( @@ -253,19 +363,28 @@ func (tv *TypedValue) Sprint(m *Machine) string { } } return nilStr - case *TypeType: - return tv.V.(TypeValue).String() case *DeclaredType: panic("should not happen") case *PackageType: return tv.V.(*PackageValue).String() case *ChanType: panic("not yet implemented") - // return tv.V.(*ChanValue).String() - case *NativeType: - return fmt.Sprintf("%v", - tv.V.(*NativeValue).Value.Interface()) + case *TypeType: + return tv.V.(TypeValue).String() default: + // The remaining types may have a nil value. + if tv.V == nil { + return nilStr + " " + tv.T.String() + } + + // *ArrayType, *SliceType, *StructType, *MapType + if ps, ok := tv.V.(protectedStringer); ok { + return ps.ProtectedString(seen) + } else if s, ok := tv.V.(fmt.Stringer); ok { + // *NativeType + return s.String() + } + if debug { panic(fmt.Sprintf( "unexpected type %s", @@ -281,6 +400,10 @@ func (tv *TypedValue) Sprint(m *Machine) string { // For gno debugging/testing. func (tv TypedValue) String() string { + return tv.ProtectedString(newSeenValues()) +} + +func (tv TypedValue) ProtectedString(seen *seenValues) string { if tv.IsUndefined() { return "(undefined)" } @@ -317,12 +440,17 @@ func (tv TypedValue) String() string { vs = fmt.Sprintf("%v", tv.GetFloat32()) case Float64Type: vs = fmt.Sprintf("%v", tv.GetFloat64()) + // Complex types that require recusion protection. default: vs = nilStr } } else { - vs = fmt.Sprintf("%v", tv.V) + vs = tv.ProtectedSprint(seen, false) + if base := baseOf(tv.T); base == StringType || base == UntypedStringType { + vs = strconv.Quote(vs) + } } + ts := tv.T.String() return fmt.Sprintf("(%s %s)", vs, ts) // TODO improve } diff --git a/gnovm/stdlibs/crypto/sha256/sha256.gno b/gnovm/stdlibs/crypto/sha256/sha256.gno index efb8ebb8e37..c36313f5482 100644 --- a/gnovm/stdlibs/crypto/sha256/sha256.gno +++ b/gnovm/stdlibs/crypto/sha256/sha256.gno @@ -1,12 +1,8 @@ package sha256 -import ( - isha256 "internal/crypto/sha256" -) - const Size = 32 // Sum returns the SHA-256 checksum of the data. -func Sum256(data []byte) [Size]byte { - return isha256.Sum256(data) -} +func Sum256(data []byte) [Size]byte { return sum256(data) } + +func sum256(data []byte) [32]byte // injected diff --git a/gnovm/stdlibs/crypto/sha256/sha256.go b/gnovm/stdlibs/crypto/sha256/sha256.go new file mode 100644 index 00000000000..c3aac1106e2 --- /dev/null +++ b/gnovm/stdlibs/crypto/sha256/sha256.go @@ -0,0 +1,7 @@ +package sha256 + +import "crypto/sha256" + +func X_sum256(data []byte) [32]byte { + return sha256.Sum256(data) +} diff --git a/gnovm/stdlibs/encoding/base64/base64.gno b/gnovm/stdlibs/encoding/base64/base64.gno index 7889a548832..ea3b0a55c2a 100644 --- a/gnovm/stdlibs/encoding/base64/base64.gno +++ b/gnovm/stdlibs/encoding/base64/base64.gno @@ -496,7 +496,9 @@ func (enc *Encoding) Decode(dst, src []byte) (n int, err error) { _ = enc.decodeMap si := 0 - for strconv.IntSize >= 64 && len(src)-si >= 8 && len(dst)-n >= 8 { + // XXX: Go source checks for strconv.IntSize >= 64 as well in this loop. + // In the gnovm, int size is always guaranteed to be 64 bits. + for len(src)-si >= 8 && len(dst)-n >= 8 { src2 := src[si : si+8] if dn, ok := assemble64( enc.decodeMap[src2[0]], diff --git a/gnovm/stdlibs/internal/crypto/sha256/sha256.gno b/gnovm/stdlibs/internal/crypto/sha256/sha256.gno deleted file mode 100644 index 458b2123f59..00000000000 --- a/gnovm/stdlibs/internal/crypto/sha256/sha256.gno +++ /dev/null @@ -1,3 +0,0 @@ -package sha256 - -// XXX injected via stdlibs/stdlibs.go diff --git a/gnovm/stdlibs/internal/math/math.gno b/gnovm/stdlibs/internal/math/math.gno deleted file mode 100644 index 42245392caf..00000000000 --- a/gnovm/stdlibs/internal/math/math.gno +++ /dev/null @@ -1,3 +0,0 @@ -package math - -// XXX injected via stdlibs/stdlibs.go diff --git a/gnovm/stdlibs/math/bits/bits_test.gno b/gnovm/stdlibs/math/bits/bits_test.gno index 2efa8874c66..55b0e3c117b 100644 --- a/gnovm/stdlibs/math/bits/bits_test.gno +++ b/gnovm/stdlibs/math/bits/bits_test.gno @@ -1094,7 +1094,7 @@ func TestDiv64PanicZero(t *testing.T) { } func TestRem32(t *testing.T) { - // Sanity check: for non-oveflowing dividends, the result is the + // Sanity check: for non-overflowing dividends, the result is the // same as the rem returned by Div32 hi, lo, y := uint32(510510), uint32(9699690), uint32(510510+1) // ensure hi < y for i := 0; i < 1000; i++ { @@ -1121,7 +1121,7 @@ func TestRem32Overflow(t *testing.T) { } func TestRem64(t *testing.T) { - // Sanity check: for non-oveflowing dividends, the result is the + // Sanity check: for non-overflowing dividends, the result is the // same as the rem returned by Div64 hi, lo, y := uint64(510510), uint64(9699690), uint64(510510+1) // ensure hi < y for i := 0; i < 1000; i++ { diff --git a/gnovm/stdlibs/math/native.gno b/gnovm/stdlibs/math/native.gno new file mode 100644 index 00000000000..b1b5684f9af --- /dev/null +++ b/gnovm/stdlibs/math/native.gno @@ -0,0 +1,21 @@ +package math + +// Float32bits returns the IEEE 754 binary representation of f, with the sign +// bit of f and the result in the same bit position. +// Float32bits(Float32frombits(x)) == x. +func Float32bits(f float32) uint32 // injected + +// Float32frombits returns the floating-point number corresponding to the IEEE +// 754 binary representation b, with the sign bit of b and the result in the +// same bit position. Float32frombits(Float32bits(x)) == x. +func Float32frombits(b uint32) float32 // injected + +// Float64bits returns the IEEE 754 binary representation of f, with the sign +// bit of f and the result in the same bit position. +// Float64bits(Float64frombits(x)) == x. +func Float64bits(f float64) uint64 // injected + +// Float64frombits returns the floating-point number corresponding to the IEEE +// 754 binary representation b, with the sign bit of b and the result in the +// same bit position. Float64frombits(Float64bits(x)) == x. +func Float64frombits(b uint64) float64 // injected diff --git a/gnovm/stdlibs/math/native.go b/gnovm/stdlibs/math/native.go new file mode 100644 index 00000000000..21021085f6d --- /dev/null +++ b/gnovm/stdlibs/math/native.go @@ -0,0 +1,8 @@ +package math + +import "math" + +func Float32bits(f float32) uint32 { return math.Float32bits(f) } +func Float32frombits(b uint32) float32 { return math.Float32frombits(b) } +func Float64bits(f float64) uint64 { return math.Float64bits(f) } +func Float64frombits(b uint64) float64 { return math.Float64frombits(b) } diff --git a/gnovm/stdlibs/math/unsafe.gno b/gnovm/stdlibs/math/unsafe.gno deleted file mode 100644 index 92d316bab2c..00000000000 --- a/gnovm/stdlibs/math/unsafe.gno +++ /dev/null @@ -1,29 +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 math - -import imath "internal/math" - -// Float32bits returns the IEEE 754 binary representation of f, -// with the sign bit of f and the result in the same bit position. -// Float32bits(Float32frombits(x)) == x. -func Float32bits(f float32) uint32 { return imath.Float32bits(f) } - -// Float32frombits returns the floating-point number corresponding -// to the IEEE 754 binary representation b, with the sign bit of b -// and the result in the same bit position. -// Float32frombits(Float32bits(x)) == x. -func Float32frombits(b uint32) float32 { return imath.Float32frombits(b) } - -// Float64bits returns the IEEE 754 binary representation of f, -// with the sign bit of f and the result in the same bit position, -// and Float64bits(Float64frombits(x)) == x. -func Float64bits(f float64) uint64 { return imath.Float64bits(f) } - -// Float64frombits returns the floating-point number corresponding -// to the IEEE 754 binary representation b, with the sign bit of b -// and the result in the same bit position. -// Float64frombits(Float64bits(x)) == x. -func Float64frombits(b uint64) float64 { return imath.Float64frombits(b) } diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go new file mode 100644 index 00000000000..f85780f2021 --- /dev/null +++ b/gnovm/stdlibs/native.go @@ -0,0 +1,764 @@ +// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. +// To regenerate it, run `go generate` from this directory. + +package stdlibs + +import ( + "reflect" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + libs_crypto_sha256 "github.com/gnolang/gno/gnovm/stdlibs/crypto/sha256" + libs_math "github.com/gnolang/gno/gnovm/stdlibs/math" + libs_std "github.com/gnolang/gno/gnovm/stdlibs/std" + libs_strconv "github.com/gnolang/gno/gnovm/stdlibs/strconv" + libs_time "github.com/gnolang/gno/gnovm/stdlibs/time" + tm2_crypto "github.com/gnolang/gno/tm2/pkg/crypto" +) + +type nativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + f func(m *gno.Machine) +} + +var nativeFuncs = [...]nativeFunc{ + { + "crypto/sha256", + "sum256", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("[]byte")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("[32]byte")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 []byte + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_crypto_sha256.X_sum256(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "math", + "Float32bits", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("float32")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("uint32")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 float32 + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_math.Float32bits(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "math", + "Float32frombits", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint32")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("float32")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint32 + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_math.Float32frombits(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "math", + "Float64bits", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("float64")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("uint64")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 float64 + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_math.Float64bits(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "math", + "Float64frombits", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint64")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("float64")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint64 + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_math.Float64frombits(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "AssertOriginCall", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + libs_std.AssertOriginCall( + m, + ) + }, + }, + { + "std", + "IsOriginCall", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + }, + func(m *gno.Machine) { + r0 := libs_std.IsOriginCall( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "CurrentRealmPath", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + r0 := libs_std.CurrentRealmPath( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetChainID", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + r0 := libs_std.GetChainID( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetHeight", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int64")}, + }, + func(m *gno.Machine) { + r0 := libs_std.GetHeight( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetOrigSend", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Coins")}, + }, + func(m *gno.Machine) { + r0 := libs_std.GetOrigSend( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetOrigCaller", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + r0 := libs_std.GetOrigCaller( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "CurrentRealm", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Realm")}, + }, + func(m *gno.Machine) { + r0 := libs_std.CurrentRealm( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "PrevRealm", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Realm")}, + }, + func(m *gno.Machine) { + r0 := libs_std.PrevRealm( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetOrigPkgAddr", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + r0 := libs_std.GetOrigPkgAddr( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetCallerAt", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_std.GetCallerAt( + m, + p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetBanker", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("BankerType")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Banker")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 libs_std.BankerType + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_std.GetBanker( + m, + p0) + + m.PushValue(r0) + }, + }, + { + "std", + "DerivePkgAddr", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_std.DerivePkgAddr(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "EncodeBech32", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("[20]byte")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 [20]byte + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0 := libs_std.EncodeBech32(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "DecodeBech32", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("Address")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r1"), Type: gno.X("[20]byte")}, + {Name: gno.N("r2"), Type: gno.X("bool")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 tm2_crypto.Bech32Address + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0, r1, r2 := libs_std.DecodeBech32(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r2).Elem(), + )) + }, + }, + { + "strconv", + "Itoa", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_strconv.Itoa(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "AppendUint", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("[]byte")}, + {Name: gno.N("p1"), Type: gno.X("uint64")}, + {Name: gno.N("p2"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("[]byte")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 []byte + rp0 = reflect.ValueOf(&p0).Elem() + p1 uint64 + rp1 = reflect.ValueOf(&p1).Elem() + p2 int + rp2 = reflect.ValueOf(&p2).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + + r0 := libs_strconv.AppendUint(p0, p1, p2) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "Atoi", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int")}, + {Name: gno.N("r1"), Type: gno.X("error")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0, r1 := libs_strconv.Atoi(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, + { + "strconv", + "CanBackquote", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_strconv.CanBackquote(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "FormatInt", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int64")}, + {Name: gno.N("p1"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int64 + rp0 = reflect.ValueOf(&p0).Elem() + p1 int + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0 := libs_strconv.FormatInt(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "FormatUint", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint64")}, + {Name: gno.N("p1"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint64 + rp0 = reflect.ValueOf(&p0).Elem() + p1 int + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0 := libs_strconv.FormatUint(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "Quote", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_strconv.Quote(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "QuoteToASCII", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_strconv.QuoteToASCII(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "time", + "now", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int64")}, + {Name: gno.N("r1"), Type: gno.X("int32")}, + {Name: gno.N("r2"), Type: gno.X("int64")}, + }, + func(m *gno.Machine) { + r0, r1, r2 := libs_time.X_now( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r2).Elem(), + )) + }, + }, +} diff --git a/gnovm/stdlibs/banker.go b/gnovm/stdlibs/std/banker.go similarity index 97% rename from gnovm/stdlibs/banker.go rename to gnovm/stdlibs/std/banker.go index 82bf1bad42a..7653f2a519f 100644 --- a/gnovm/stdlibs/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -1,4 +1,4 @@ -package stdlibs +package std import ( "fmt" @@ -21,10 +21,10 @@ type Banker interface { } // Used in std.GetBanker(options). -// Also available as Gno in stdlibs/std/banker.go +// Also available as Gno in stdlibs/std/banker.gno type BankerType uint8 -// Also available as Gno in stdlibs/std/banker.go +// Also available as Gno in stdlibs/std/banker.gno const ( // Can only read state. BankerTypeReadonly BankerType = iota diff --git a/gnovm/stdlibs/context.go b/gnovm/stdlibs/std/context.go similarity index 96% rename from gnovm/stdlibs/context.go rename to gnovm/stdlibs/std/context.go index 5f140c344d4..c50e2e5e1b9 100644 --- a/gnovm/stdlibs/context.go +++ b/gnovm/stdlibs/std/context.go @@ -1,4 +1,4 @@ -package stdlibs +package std import ( "github.com/gnolang/gno/tm2/pkg/crypto" diff --git a/gnovm/stdlibs/frame.go b/gnovm/stdlibs/std/frame.go similarity index 94% rename from gnovm/stdlibs/frame.go rename to gnovm/stdlibs/std/frame.go index e428bb1776d..2948813ad0f 100644 --- a/gnovm/stdlibs/frame.go +++ b/gnovm/stdlibs/std/frame.go @@ -1,4 +1,4 @@ -package stdlibs +package std import "github.com/gnolang/gno/tm2/pkg/crypto" diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno new file mode 100644 index 00000000000..2f7da810bcb --- /dev/null +++ b/gnovm/stdlibs/std/native.gno @@ -0,0 +1,18 @@ +package std + +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func CurrentRealmPath() string // injected +func GetChainID() string // injected +func GetHeight() int64 // injected +func GetOrigSend() Coins // injected +func GetOrigCaller() Address // injected +func CurrentRealm() Realm // injected +func PrevRealm() Realm // injected +func GetOrigPkgAddr() Address // injected +func GetCallerAt(n int) Address // injected +func GetBanker(bt BankerType) Banker // injected +func DerivePkgAddr(pkgPath string) Address // injected + +func EncodeBech32(prefix string, bz [20]byte) Address // injected +func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) // injected diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go new file mode 100644 index 00000000000..5f6cc8a0061 --- /dev/null +++ b/gnovm/stdlibs/std/native.go @@ -0,0 +1,187 @@ +package std + +import ( + "fmt" + "reflect" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/tm2/pkg/bech32" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" +) + +func AssertOriginCall(m *gno.Machine) { + if !IsOriginCall(m) { + m.Panic(typedString("invalid non-origin call")) + } +} + +func IsOriginCall(m *gno.Machine) bool { + return len(m.Frames) == 2 +} + +func CurrentRealmPath(m *gno.Machine) string { + if m.Realm != nil { + return m.Realm.Path + } + return "" +} + +func GetChainID(m *gno.Machine) string { + return m.Context.(ExecContext).ChainID +} + +func GetHeight(m *gno.Machine) int64 { + return m.Context.(ExecContext).Height +} + +func GetOrigSend(m *gno.Machine) std.Coins { + return m.Context.(ExecContext).OrigSend +} + +func GetOrigCaller(m *gno.Machine) crypto.Bech32Address { + return m.Context.(ExecContext).OrigCaller +} + +func CurrentRealm(m *gno.Machine) Realm { + var ( + ctx = m.Context.(ExecContext) + // Default lastCaller is OrigCaller, the signer of the tx + lastCaller = ctx.OrigCaller + lastPkgPath = "" + ) + + for i := m.NumFrames() - 1; i > 0; i-- { + fr := m.Frames[i] + if fr.LastPackage != nil && fr.LastPackage.IsRealm() { + lastCaller = fr.LastPackage.GetPkgAddr().Bech32() + lastPkgPath = fr.LastPackage.PkgPath + break + } + } + + return Realm{ + addr: lastCaller, + pkgPath: lastPkgPath, + } +} + +func PrevRealm(m *gno.Machine) Realm { + var ( + ctx = m.Context.(ExecContext) + // Default lastCaller is OrigCaller, the signer of the tx + lastCaller = ctx.OrigCaller + lastPkgPath = "" + ) + + for i := m.NumFrames() - 1; i > 0; i-- { + fr := m.Frames[i] + if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { + // Ignore non-realm frame + continue + } + pkgPath := fr.LastPackage.PkgPath + // The first realm we encounter will be the one calling + // this function; to get the calling realm determine the first frame + // where fr.LastPackage changes. + if lastPkgPath == "" { + lastPkgPath = pkgPath + } else if lastPkgPath == pkgPath { + continue + } else { + lastCaller = fr.LastPackage.GetPkgAddr().Bech32() + lastPkgPath = pkgPath + break + } + } + + // Empty the pkgPath if we return a user + if ctx.OrigCaller == lastCaller { + lastPkgPath = "" + } + + return Realm{ + addr: lastCaller, + pkgPath: lastPkgPath, + } +} + +func GetOrigPkgAddr(m *gno.Machine) crypto.Bech32Address { + return m.Context.(ExecContext).OrigPkgAddr +} + +func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { + if n <= 0 { + fmt.Println("QWEQWEQWEQWE", n) + m.Panic(typedString("GetCallerAt requires positive arg")) + return "" + } + if n > m.NumFrames() { + // NOTE: the last frame's LastPackage + // is set to the original non-frame + // package, so need this check. + m.Panic(typedString("frame not found")) + return "" + } + if n == m.NumFrames() { + // This makes it consistent with GetOrigCaller. + ctx := m.Context.(ExecContext) + return ctx.OrigCaller + } + return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() +} + +func GetBanker(m *gno.Machine, bankerType BankerType) gno.TypedValue { + ctx := m.Context.(ExecContext) + banker := ctx.Banker + switch bankerType { + case BankerTypeReadonly: + banker = NewReadonlyBanker(banker) + case BankerTypeOrigSend: + banker = NewOrigSendBanker(banker, ctx.OrigPkgAddr, ctx.OrigSend, ctx.OrigSendSpent) + case BankerTypeRealmSend: + banker = NewRealmSendBanker(banker, ctx.OrigPkgAddr) + case BankerTypeRealmIssue: + banker = banker + default: + panic("should not happen") // defensive + } + m.Alloc.AllocateStruct() // defensive; native space not allocated. + m.Alloc.AllocateStructFields(10) // defensive 10; native space not allocated. + + // make gno bankAdapter{rv} + btv := gno.Go2GnoNativeValue(m.Alloc, reflect.ValueOf(banker)) + bsv := m.Alloc.NewStructWithFields(btv) + bankAdapterType := m.Store.GetType(gno.DeclaredTypeID("std", "bankAdapter")) + res0 := gno.TypedValue{T: bankAdapterType, V: bsv} + + return res0 +} + +func EncodeBech32(prefix string, bytes [20]byte) crypto.Bech32Address { + b32, err := bech32.ConvertAndEncode(prefix, bytes[:]) + if err != nil { + panic(err) // should not happen + } + return crypto.Bech32Address(b32) +} + +func DerivePkgAddr(pkgPath string) crypto.Bech32Address { + return gno.DerivePkgAddr(pkgPath).Bech32() +} + +func DecodeBech32(addr crypto.Bech32Address) (prefix string, bytes [20]byte, ok bool) { + prefix, bz, err := bech32.Decode(string(addr)) + if err != nil || len(bz) != 20 { + return "", [20]byte{}, false + } + // TODO: can be simplified when we switch to go1.20 in go mod to be a simple [20]byte(bz) + copy(bytes[:], bz) + return prefix, bytes, true +} + +func typedString(s gno.StringValue) gno.TypedValue { + tv := gno.TypedValue{T: gno.StringType} + tv.SetString(s) + return tv +} diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 8931266eb9a..22054613c03 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -1,583 +1,30 @@ package stdlibs +//go:generate go run github.com/gnolang/gno/misc/genstd + import ( - "crypto/sha256" - "math" "reflect" - "strconv" - "time" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/tm2/pkg/bech32" + libsstd "github.com/gnolang/gno/gnovm/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" ) +type ExecContext = libsstd.ExecContext + func InjectNativeMappings(store gno.Store) { store.AddGo2GnoMapping(reflect.TypeOf(crypto.Bech32Address("")), "std", "Address") store.AddGo2GnoMapping(reflect.TypeOf(std.Coins{}), "std", "Coins") store.AddGo2GnoMapping(reflect.TypeOf(std.Coin{}), "std", "Coin") - store.AddGo2GnoMapping(reflect.TypeOf(Realm{}), "std", "Realm") -} - -func InjectPackage(store gno.Store, pn *gno.PackageNode) { - switch pn.PkgPath { - case "internal/crypto/sha256": - pn.DefineNative("Sum256", - gno.Flds( // params - "data", "[]byte", - ), - gno.Flds( // results - "bz", "[32]byte", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - bz := []byte(nil) - - if arg0.V != nil { - slice := arg0.V.(*gno.SliceValue) - array := slice.GetBase(m.Store) - bz = array.GetReadonlyBytes()[:slice.Length] - } - - hash := sha256.Sum256(bz) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(hash), - ) - m.PushValue(res0) - }, - ) - case "internal/math": - pn.DefineNative("Float32bits", - gno.Flds( // params - "f", "float32", - ), - gno.Flds( // results - "b", "uint32", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - res0 := typedUint32(math.Float32bits(arg0.GetFloat32())) - m.PushValue(res0) - }, - ) - pn.DefineNative("Float32frombits", - gno.Flds( // params - "b", "uint32", - ), - gno.Flds( // results - "f", "float32", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - res0 := typedFloat32(math.Float32frombits(arg0.GetUint32())) - m.PushValue(res0) - }, - ) - pn.DefineNative("Float64bits", - gno.Flds( // params - "f", "float64", - ), - gno.Flds( // results - "b", "uint64", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - res0 := typedUint64(math.Float64bits(arg0.GetFloat64())) - m.PushValue(res0) - }, - ) - pn.DefineNative("Float64frombits", - gno.Flds( // params - "b", "uint64", - ), - gno.Flds( // results - "f", "float64", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - res0 := typedFloat64(math.Float64frombits(arg0.GetUint64())) - m.PushValue(res0) - }, - ) - case "internal/os": - pn.DefineNative("Now", - gno.Flds( // params - ), - gno.Flds( // results - "sec", "int64", - "nsec", "int32", - "mono", "int64", - ), - func(m *gno.Machine) { - if m.Context == nil { - res0 := typedInt64(0) - res1 := typedInt32(0) - res2 := typedInt64(0) - m.PushValue(res0) - m.PushValue(res1) - m.PushValue(res2) - } else { - ctx := m.Context.(ExecContext) - res0 := typedInt64(ctx.Timestamp) - res1 := typedInt32(int32(ctx.TimestampNano)) - res2 := typedInt64(ctx.Timestamp*int64(time.Second) + ctx.TimestampNano) - m.PushValue(res0) - m.PushValue(res1) - m.PushValue(res2) - } - }, - ) - // case "internal/os_test": - // XXX defined in tests/imports.go - case "strconv": - pn.DefineGoNativeValue("Itoa", strconv.Itoa) - pn.DefineGoNativeValue("Atoi", strconv.Atoi) - pn.DefineGoNativeValue("FormatInt", strconv.FormatInt) - pn.DefineGoNativeValue("FormatUint", strconv.FormatUint) - pn.DefineGoNativeValue("Quote", strconv.Quote) - pn.DefineGoNativeValue("QuoteToASCII", strconv.QuoteToASCII) - pn.DefineGoNativeValue("CanBackquote", strconv.CanBackquote) - pn.DefineGoNativeValue("IntSize", strconv.IntSize) - pn.DefineGoNativeValue("AppendUint", strconv.AppendUint) - case "std": - // NOTE: some of these are overridden in tests/imports.go - // Also see stdlibs/InjectPackage. - pn.DefineNative("AssertOriginCall", - gno.Flds( // params - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - isOrigin := len(m.Frames) == 2 - if !isOrigin { - m.Panic(typedString("invalid non-origin call")) - return - } - }, - ) - pn.DefineNative("IsOriginCall", - gno.Flds( // params - ), - gno.Flds( // results - "isOrigin", "bool", - ), - func(m *gno.Machine) { - isOrigin := len(m.Frames) == 2 - res0 := gno.TypedValue{T: gno.BoolType} - res0.SetBool(isOrigin) - m.PushValue(res0) - }, - ) - pn.DefineNative("CurrentRealmPath", - gno.Flds( // params - ), - gno.Flds( // results - "", "string", - ), - func(m *gno.Machine) { - realmPath := "" - if m.Realm != nil { - realmPath = m.Realm.Path - } - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(realmPath), - ) - m.PushValue(res0) - }, - ) - pn.DefineNative("GetChainID", - gno.Flds( // params - ), - gno.Flds( // results - "", "string", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(ctx.ChainID), - ) - m.PushValue(res0) - }, - ) - pn.DefineNative("GetHeight", - gno.Flds( // params - ), - gno.Flds( // results - "", "int64", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(ctx.Height), - ) - m.PushValue(res0) - }, - ) - pn.DefineNative("GetOrigSend", - gno.Flds( // params - ), - gno.Flds( // results - "", "Coins", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(ctx.OrigSend), - ) - coinT := store.GetType(gno.DeclaredTypeID("std", "Coin")) - coinsT := store.GetType(gno.DeclaredTypeID("std", "Coins")) - res0.T = coinsT - av := res0.V.(*gno.SliceValue).Base.(*gno.ArrayValue) - for i := range av.List { - av.List[i].T = coinT - } - m.PushValue(res0) - }, - ) - pn.DefineNative("GetOrigCaller", - gno.Flds( // params - ), - gno.Flds( // results - "", "Address", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(ctx.OrigCaller), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - pn.DefineNative("CurrentRealm", - gno.Flds( // params - ), - gno.Flds( // results - "", "Realm", - ), - func(m *gno.Machine) { - var ( - ctx = m.Context.(ExecContext) - // Default lastCaller is OrigCaller, the signer of the tx - lastCaller = ctx.OrigCaller - lastPkgPath = "" - ) - - for i := m.NumFrames() - 1; i > 0; i-- { - fr := m.Frames[i] - if fr.LastPackage != nil && fr.LastPackage.IsRealm() { - lastCaller = fr.LastPackage.GetPkgAddr().Bech32() - lastPkgPath = fr.LastPackage.PkgPath - break - } - } - - // Return the result - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(Realm{ - addr: lastCaller, - pkgPath: lastPkgPath, - }), - ) - - realmT := store.GetType(gno.DeclaredTypeID("std", "Realm")) - res0.T = realmT - m.PushValue(res0) - }, - ) - pn.DefineNative("PrevRealm", - gno.Flds( // params - ), - gno.Flds( // results - "", "Realm", - ), - func(m *gno.Machine) { - var ( - ctx = m.Context.(ExecContext) - // Default lastCaller is OrigCaller, the signer of the tx - lastCaller = ctx.OrigCaller - lastPkgPath = "" - ) - - for i := m.NumFrames() - 1; i > 0; i-- { - fr := m.Frames[i] - if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { - // Ignore non-realm frame - continue - } - pkgPath := fr.LastPackage.PkgPath - // The first realm we encounter will be the one calling - // this function; to get the calling realm determine the first frame - // where fr.LastPackage changes. - if lastPkgPath == "" { - lastPkgPath = pkgPath - } else if lastPkgPath == pkgPath { - continue - } else { - lastCaller = fr.LastPackage.GetPkgAddr().Bech32() - lastPkgPath = pkgPath - break - } - } - - // Empty the pkgPath if we return a user - if ctx.OrigCaller == lastCaller { - lastPkgPath = "" - } - - // Return the result - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(Realm{ - addr: lastCaller, - pkgPath: lastPkgPath, - }), - ) - - realmT := store.GetType(gno.DeclaredTypeID("std", "Realm")) - res0.T = realmT - m.PushValue(res0) - }, - ) - pn.DefineNative("GetOrigPkgAddr", - gno.Flds( // params - ), - gno.Flds( // results - "", "Address", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(ctx.OrigPkgAddr), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - pn.DefineNative("GetCallerAt", - gno.Flds( // params - "n", "int", - ), - gno.Flds( // results - "", "Address", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - n := arg0.GetInt() - if n <= 0 { - m.Panic(typedString("GetCallerAt requires positive arg")) - return - } - if n > m.NumFrames() { - // NOTE: the last frame's LastPackage - // is set to the original non-frame - // package, so need this check. - m.Panic(typedString("frame not found")) - return - } - var pkgAddr string - if n == m.NumFrames() { - // This makes it consistent with GetOrigCaller. - ctx := m.Context.(ExecContext) - pkgAddr = string(ctx.OrigCaller) - } else { - pkgAddr = string(m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) - } - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(pkgAddr), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - pn.DefineNative("GetBanker", - gno.Flds( // params - "bankerType", "BankerType", - ), - gno.Flds( // results - "", "Banker", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - arg0 := m.LastBlock().GetParams1().TV - bankerType := BankerType(arg0.GetUint8()) - banker := ctx.Banker - switch bankerType { - case BankerTypeReadonly: - banker = NewReadonlyBanker(banker) - case BankerTypeOrigSend: - banker = NewOrigSendBanker(banker, ctx.OrigPkgAddr, ctx.OrigSend, ctx.OrigSendSpent) - case BankerTypeRealmSend: - banker = NewRealmSendBanker(banker, ctx.OrigPkgAddr) - case BankerTypeRealmIssue: - banker = banker - default: - panic("should not happen") // defensive - } - rv := reflect.ValueOf(banker) - m.Alloc.AllocateStruct() // defensive; native space not allocated. - m.Alloc.AllocateStructFields(10) // defensive 10; native space not allocated. - - // make gno bankAdapter{rv} - btv := gno.Go2GnoNativeValue(m.Alloc, rv) - bsv := m.Alloc.NewStructWithFields(btv) - bankAdapterType := store.GetType(gno.DeclaredTypeID("std", "bankAdapter")) - res0 := gno.TypedValue{T: bankAdapterType, V: bsv} - m.PushValue(res0) - }, - ) - pn.DefineNative("EncodeBech32", - gno.Flds( // params - "prefix", "string", - "bytes", "[20]byte", - ), - gno.Flds( // results - "addr", "Address", - ), - func(m *gno.Machine) { - arg0, arg1 := m.LastBlock().GetParams2() - prefix := arg0.TV.GetString() - bz := arg1.TV.V.(*gno.ArrayValue).GetReadonlyBytes() - if len(bz) != crypto.AddressSize { - panic("should not happen") - } - b32, err := bech32.ConvertAndEncode(prefix, bz) - if err != nil { - panic(err) // should not happen - } - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(b32), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - pn.DefineNative("DecodeBech32", - gno.Flds( // params - "addr", "Address", - ), - gno.Flds( // results - "prefix", "string", - "bytes", "[20]byte", - "ok", "bool", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1() - addr := arg0.TV.GetString() - prefix, bz, err := bech32.Decode(addr) - if err != nil || len(bz) != 20 { - m.PushValue(typedString(m.Alloc.NewString(""))) - m.PushValue(typedByteArray(20, m.Alloc.NewDataArray(20))) - m.PushValue(typedBool(false)) - } else { - m.PushValue(typedString(m.Alloc.NewString(prefix))) - m.PushValue(typedByteArray(20, m.Alloc.NewArrayFromData(bz))) - m.PushValue(typedBool(true)) - } - }, - ) - pn.DefineNative("DerivePkgAddr", - gno.Flds( // params - "pkgPath", "string", - ), - gno.Flds( // results - "addr", "Address", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - pkgPath := arg0.GetString() - pkgAddr := gno.DerivePkgAddr(pkgPath).Bech32() - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(pkgAddr), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - } -} - -func typedInt32(i32 int32) gno.TypedValue { - tv := gno.TypedValue{T: gno.Int32Type} - tv.SetInt32(i32) - return tv -} - -func typedInt64(i64 int64) gno.TypedValue { - tv := gno.TypedValue{T: gno.Int64Type} - tv.SetInt64(i64) - return tv -} - -func typedUint32(u32 uint32) gno.TypedValue { - tv := gno.TypedValue{T: gno.Uint32Type} - tv.SetUint32(u32) - return tv -} - -func typedUint64(u64 uint64) gno.TypedValue { - tv := gno.TypedValue{T: gno.Uint64Type} - tv.SetUint64(u64) - return tv -} - -func typedFloat32(f32 float32) gno.TypedValue { - tv := gno.TypedValue{T: gno.Float32Type} - tv.SetFloat32(f32) - return tv -} - -func typedFloat64(f64 float64) gno.TypedValue { - tv := gno.TypedValue{T: gno.Float64Type} - tv.SetFloat64(f64) - return tv -} - -func typedString(s gno.StringValue) gno.TypedValue { - tv := gno.TypedValue{T: gno.StringType} - tv.SetString(s) - return tv -} - -func typedBool(b bool) gno.TypedValue { - tv := gno.TypedValue{T: gno.BoolType} - tv.SetBool(b) - return tv + store.AddGo2GnoMapping(reflect.TypeOf(libsstd.Realm{}), "std", "Realm") } -func typedByteArray(ln int, bz *gno.ArrayValue) gno.TypedValue { - if bz != nil && bz.GetLength() != ln { - panic("array length mismatch") +func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { + for _, nf := range nativeFuncs { + if nf.gnoPkg == pkgPath && name == nf.gnoFunc { + return nf.f + } } - tv := gno.TypedValue{T: &gno.ArrayType{Len: ln, Elt: gno.Uint8Type}, V: bz} - return tv + return nil } diff --git a/gnovm/stdlibs/strconv/strconv.gno b/gnovm/stdlibs/strconv/strconv.gno index dce62b890e3..bc7b5d8d1b6 100644 --- a/gnovm/stdlibs/strconv/strconv.gno +++ b/gnovm/stdlibs/strconv/strconv.gno @@ -1,4 +1,10 @@ package strconv -// NOTE: currently these are implemented as native functions. -// See InjectNatives(). +func Itoa(n int) string // injected +func AppendUint(dst []byte, i uint64, base int) []byte // injected +func Atoi(s string) (int, error) // injected +func CanBackquote(s string) bool // injected +func FormatInt(i int64, base int) string // injected +func FormatUint(i uint64, base int) string // injected +func Quote(s string) string // injected +func QuoteToASCII(s string) string // injected diff --git a/gnovm/stdlibs/strconv/strconv.go b/gnovm/stdlibs/strconv/strconv.go new file mode 100644 index 00000000000..782a63e84b6 --- /dev/null +++ b/gnovm/stdlibs/strconv/strconv.go @@ -0,0 +1,12 @@ +package strconv + +import "strconv" + +func Itoa(n int) string { return strconv.Itoa(n) } +func AppendUint(dst []byte, i uint64, base int) []byte { return strconv.AppendUint(dst, i, base) } +func Atoi(s string) (int, error) { return strconv.Atoi(s) } +func CanBackquote(s string) bool { return strconv.CanBackquote(s) } +func FormatInt(i int64, base int) string { return strconv.FormatInt(i, base) } +func FormatUint(i uint64, base int) string { return strconv.FormatUint(i, base) } +func Quote(s string) string { return strconv.Quote(s) } +func QuoteToASCII(r string) string { return strconv.QuoteToASCII(r) } diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 7b7e45ba9b6..ceed70452f6 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -80,8 +80,6 @@ package time import ( "errors" - - ios "internal/os" // XXX to access time. // XXX _ "unsafe" // for go:linkname ) @@ -1071,17 +1069,13 @@ func daysSinceEpoch(year int) uint64 { return d } -/* XXX replaced with ios.Now() -// Provided by package runtime. -func now() (sec int64, nsec int32, mono int64) -*/ +func now() (sec int64, nsec int32, mono int64) // injected -// XXX SHIM // runtimeNano returns the current value of the runtime clock in nanoseconds. // //go:linkname runtimeNano runtime.nanotime func runtimeNano() int64 { - _, _, mono := ios.Now() // XXX now() + _, _, mono := now() return mono } @@ -1095,7 +1089,7 @@ var startNano int64 = runtimeNano() - 1 // Now returns the current local time. func Now() Time { - sec, nsec, mono := ios.Now() // XXX now() + sec, nsec, mono := now() mono -= startNano sec += unixToInternal - minWall if uint64(sec)>>33 != 0 { diff --git a/gnovm/stdlibs/time/time.go b/gnovm/stdlibs/time/time.go new file mode 100644 index 00000000000..8c1c768715e --- /dev/null +++ b/gnovm/stdlibs/time/time.go @@ -0,0 +1,17 @@ +package time + +import ( + "time" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs/std" +) + +func X_now(m *gno.Machine) (sec int64, nsec int32, mono int64) { + if m == nil || m.Context == nil { + return 0, 0, 0 + } + + ctx := m.Context.(std.ExecContext) + return ctx.Timestamp, int32(ctx.TimestampNano), ctx.Timestamp*int64(time.Second) + ctx.TimestampNano +} diff --git a/gnovm/tests/challenges/not_a_type.gno b/gnovm/tests/challenges/not_a_type.gno new file mode 100644 index 00000000000..ed8adb9983a --- /dev/null +++ b/gnovm/tests/challenges/not_a_type.gno @@ -0,0 +1,10 @@ +package main + +var T struct{} + +func (t T) do() {} + +func main() {} + +// Error: +// T (variable of type struct{}) is not a type diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 451bf0677dc..a021628a385 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -183,6 +183,7 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { }, }, } + // run decls and init functions. m.RunMemPackage(memPkg, true) // reconstruct machine and clear store cache. // whether package is realm or not, since non-realm diff --git a/gnovm/tests/files/a47.gno b/gnovm/tests/files/a47.gno new file mode 100644 index 00000000000..2775547a3e8 --- /dev/null +++ b/gnovm/tests/files/a47.gno @@ -0,0 +1,32 @@ +package main + +type S struct { + i int +} + +func main() { + sArr := make([]S, 0, 4) + sArr = append(sArr, S{1}, S{2}, S{3}) + println(sArr[0].i, sArr[1].i, sArr[2].i) + + newArr := append(sArr[:0], sArr[1:]...) + + // The append modified the underlying array because it was within capacity. + println(len(sArr) == 3) + println(sArr[0].i, sArr[1].i, sArr[2].i) + + // It generated a new slice that references the same array. + println(len(newArr) == 2) + println(newArr[0].i, newArr[1].i) + + // The struct should have been copied, not referenced. + println(&sArr[2] != &newArr[1]) +} + +// Output: +// 1 2 3 +// true +// 2 3 3 +// true +// 2 3 +// true diff --git a/gnovm/tests/files/base_conv0.gno b/gnovm/tests/files/base_conv0.gno new file mode 100644 index 00000000000..381d10cdc98 --- /dev/null +++ b/gnovm/tests/files/base_conv0.gno @@ -0,0 +1,27 @@ +package main + +func main() { + // binary + println(0b1010) + println(0B1010) + + // decimal + println(10) + + // octal + println(0o12) + println(012) + + // hex + println(0xA) + println(0xa) +} + +// Output: +// 10 +// 10 +// 10 +// 10 +// 10 +// 10 +// 10 diff --git a/gnovm/tests/files/base_conv1.gno b/gnovm/tests/files/base_conv1.gno new file mode 100644 index 00000000000..6ccf8d0fe70 --- /dev/null +++ b/gnovm/tests/files/base_conv1.gno @@ -0,0 +1,27 @@ +package main + +func main() { + // binary + println(-0b1010) + println(-0B1010) + + // decimal + println(-10) + + // octal + println(-0o12) + println(-012) + + // hex + println(-0xA) + println(-0xa) +} + +// Output: +// -10 +// -10 +// -10 +// -10 +// -10 +// -10 +// -10 diff --git a/gnovm/tests/files/bool6.gno b/gnovm/tests/files/bool6.gno new file mode 100644 index 00000000000..ad4d832036c --- /dev/null +++ b/gnovm/tests/files/bool6.gno @@ -0,0 +1,12 @@ +package main + +func main() { + println(X()) +} + +func X() string { + return "hello" || "world" +} + +// Error: +// main/files/bool6.gno:8: operands of boolean operators must evaluate to boolean typed values diff --git a/gnovm/tests/files/bool7.gno b/gnovm/tests/files/bool7.gno new file mode 100644 index 00000000000..ba8be09dc7c --- /dev/null +++ b/gnovm/tests/files/bool7.gno @@ -0,0 +1,16 @@ +package main + +// Ensure, when comparing evaluated boolean operand types, that the kinds produced +// are the same when one operand is typed and the other is untyped. +func main() { + println(boolAndTrue(true)) + println(boolAndTrue(false)) +} + +func boolAndTrue(b bool) bool { + return b && true +} + +// Output: +// true +// false diff --git a/gnovm/tests/files/extern/redeclaration1/README.md b/gnovm/tests/files/extern/redeclaration1/README.md new file mode 100644 index 00000000000..6c8514c3ca1 --- /dev/null +++ b/gnovm/tests/files/extern/redeclaration1/README.md @@ -0,0 +1,4 @@ +This package is invalid because 'a' is defined twice. +NOTE: the Go parser itself returns an error for redefinitions in the same file, +but testing for redeclarations across files requires our own custom logic. +(arguably we should check ourself either way). diff --git a/gnovm/tests/files/extern/redeclaration1/redeclaration.gno b/gnovm/tests/files/extern/redeclaration1/redeclaration.gno new file mode 100644 index 00000000000..b9b17a50e80 --- /dev/null +++ b/gnovm/tests/files/extern/redeclaration1/redeclaration.gno @@ -0,0 +1,3 @@ +package redeclaration + +var a = 1 diff --git a/gnovm/tests/files/extern/redeclaration1/redeclaration2.gno b/gnovm/tests/files/extern/redeclaration1/redeclaration2.gno new file mode 100644 index 00000000000..11f2f095cb4 --- /dev/null +++ b/gnovm/tests/files/extern/redeclaration1/redeclaration2.gno @@ -0,0 +1,4 @@ +package redeclaration + +// redeclared +var a = 2 diff --git a/gnovm/tests/files/extern/redeclaration2/README.md b/gnovm/tests/files/extern/redeclaration2/README.md new file mode 100644 index 00000000000..6c8514c3ca1 --- /dev/null +++ b/gnovm/tests/files/extern/redeclaration2/README.md @@ -0,0 +1,4 @@ +This package is invalid because 'a' is defined twice. +NOTE: the Go parser itself returns an error for redefinitions in the same file, +but testing for redeclarations across files requires our own custom logic. +(arguably we should check ourself either way). diff --git a/gnovm/tests/files/extern/redeclaration2/redeclaration.gno b/gnovm/tests/files/extern/redeclaration2/redeclaration.gno new file mode 100644 index 00000000000..ef6cac198db --- /dev/null +++ b/gnovm/tests/files/extern/redeclaration2/redeclaration.gno @@ -0,0 +1,3 @@ +package redeclaration + +func a() int { return 1 } diff --git a/gnovm/tests/files/extern/redeclaration2/redeclaration2.gno b/gnovm/tests/files/extern/redeclaration2/redeclaration2.gno new file mode 100644 index 00000000000..3f58a963502 --- /dev/null +++ b/gnovm/tests/files/extern/redeclaration2/redeclaration2.gno @@ -0,0 +1,4 @@ +package redeclaration + +// redeclared (with same signature) +func a() int { return 2 } diff --git a/gnovm/tests/files/extern/redeclaration3/README.md b/gnovm/tests/files/extern/redeclaration3/README.md new file mode 100644 index 00000000000..6c8514c3ca1 --- /dev/null +++ b/gnovm/tests/files/extern/redeclaration3/README.md @@ -0,0 +1,4 @@ +This package is invalid because 'a' is defined twice. +NOTE: the Go parser itself returns an error for redefinitions in the same file, +but testing for redeclarations across files requires our own custom logic. +(arguably we should check ourself either way). diff --git a/gnovm/tests/files/extern/redeclaration3/redeclaration.gno b/gnovm/tests/files/extern/redeclaration3/redeclaration.gno new file mode 100644 index 00000000000..380a1a4288f --- /dev/null +++ b/gnovm/tests/files/extern/redeclaration3/redeclaration.gno @@ -0,0 +1,5 @@ +package redeclaration + +type a struct{} + +func (_ a) method() int { return 1 } diff --git a/gnovm/tests/files/extern/redeclaration3/redeclaration2.gno b/gnovm/tests/files/extern/redeclaration3/redeclaration2.gno new file mode 100644 index 00000000000..c2256bdf7cb --- /dev/null +++ b/gnovm/tests/files/extern/redeclaration3/redeclaration2.gno @@ -0,0 +1,4 @@ +package redeclaration + +// redeclared (with same signature) +func (_ a) method() int { return 2 } diff --git a/gnovm/tests/files/float5_native.gno b/gnovm/tests/files/float5_native.gno index 1050d277605..b2d9228c0bc 100644 --- a/gnovm/tests/files/float5_native.gno +++ b/gnovm/tests/files/float5_native.gno @@ -1,13 +1,13 @@ package main import ( - imath "internal/math" + "math" ) func main() { // test float64 f := float64(0.3) - x := imath.Float64bits(f) + x := math.Float64bits(f) e := uint(40) println(f, x, e, (1 << (64 - e))) diff --git a/gnovm/tests/files/float5_stdlibs.gno b/gnovm/tests/files/float5_stdlibs.gno index 8449ddff820..b3d8cd84713 100644 --- a/gnovm/tests/files/float5_stdlibs.gno +++ b/gnovm/tests/files/float5_stdlibs.gno @@ -2,13 +2,11 @@ package main import ( "math" - - imath "internal/math" ) func main() { - println(math.MaxFloat32, imath.Float32bits(math.MaxFloat32)) - println(math.MaxFloat64, imath.Float64bits(math.MaxFloat64)) + println(math.MaxFloat32, math.Float32bits(math.MaxFloat32)) + println(math.MaxFloat64, math.Float64bits(math.MaxFloat64)) } // Output: diff --git a/gnovm/tests/files/math4.gno b/gnovm/tests/files/math4.gno new file mode 100644 index 00000000000..83bfb44f7b0 --- /dev/null +++ b/gnovm/tests/files/math4.gno @@ -0,0 +1,19 @@ +package main + +func main() { + println(-3.0 * 1.0 / 2.0) // -1.5 + println(-3.0 * 1.0 / 2) // -1.5 + println(-3.0 * 1 / 2.0) // -1.5 + + println(-3 * 1.0 / 2.0) // 1.5 + println(-3 * 1.0 / 2) // 1.5 + println(-3 * 1 / 2.0) // 1.5 +} + +// Output: +// -1.5 +// -1.5 +// -1.5 +// -1.5 +// -1.5 +// -1.5 diff --git a/gnovm/tests/files/print1.gno b/gnovm/tests/files/print1.gno new file mode 100644 index 00000000000..606759a5c05 --- /dev/null +++ b/gnovm/tests/files/print1.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var a []string + println(a) +} + +// Output: +// nil []string diff --git a/gnovm/tests/files/redeclaration10.gno b/gnovm/tests/files/redeclaration10.gno new file mode 100644 index 00000000000..01584b1755c --- /dev/null +++ b/gnovm/tests/files/redeclaration10.gno @@ -0,0 +1,12 @@ +package main + +import _ "github.com/gnolang/gno/_test/redeclaration3" + +func main() { + println("should not happen") +} + +// XXX show what was redeclared. + +// Error: +// running package "github.com/gnolang/gno/_test/redeclaration3": duplicate declarations not allowed diff --git a/gnovm/tests/files/redeclaration6.gno b/gnovm/tests/files/redeclaration6.gno new file mode 100644 index 00000000000..25e36fa61aa --- /dev/null +++ b/gnovm/tests/files/redeclaration6.gno @@ -0,0 +1,12 @@ +package main + +import _ "github.com/gnolang/gno/_test/redeclaration1" + +func main() { + println("should not happen") +} + +// XXX show what was redeclared. + +// Error: +// running package "github.com/gnolang/gno/_test/redeclaration1": duplicate declarations not allowed diff --git a/gnovm/tests/files/redeclaration7.gno b/gnovm/tests/files/redeclaration7.gno new file mode 100644 index 00000000000..d987a58d7eb --- /dev/null +++ b/gnovm/tests/files/redeclaration7.gno @@ -0,0 +1,13 @@ +package main + +func f1() int { return 1 } + +func f1() int { return 2 } + +func main() { + println("hello", f1()) +} + +// Error: +// files/redeclaration7.gno:5:6: f1 redeclared in this block +// previous declaration at files/redeclaration7.gno:3:6 diff --git a/gnovm/tests/files/redeclaration8.gno b/gnovm/tests/files/redeclaration8.gno new file mode 100644 index 00000000000..d0e5b958030 --- /dev/null +++ b/gnovm/tests/files/redeclaration8.gno @@ -0,0 +1,12 @@ +package main + +import _ "github.com/gnolang/gno/_test/redeclaration2" + +func main() { + println("should not happen") +} + +// XXX show what was redeclared. + +// Error: +// running package "github.com/gnolang/gno/_test/redeclaration2": duplicate declarations not allowed diff --git a/gnovm/tests/files/redeclaration9.gno b/gnovm/tests/files/redeclaration9.gno new file mode 100644 index 00000000000..89a63683c13 --- /dev/null +++ b/gnovm/tests/files/redeclaration9.gno @@ -0,0 +1,14 @@ +package main + +type a struct{} + +func (_ a) method() int { return 1 } + +func (_ a) method() int { return 2 } + +func main() { + println("hello") +} + +// Error: +// main/files/redeclaration9.gno:7: redeclaration of method a.method diff --git a/gnovm/tests/files/zrealm0.gno b/gnovm/tests/files/zrealm0.gno index 1b8f37540b3..7578781e503 100644 --- a/gnovm/tests/files/zrealm0.gno +++ b/gnovm/tests/files/zrealm0.gno @@ -56,6 +56,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm1.gno b/gnovm/tests/files/zrealm1.gno index 1dea983a49d..d90c5e8621a 100644 --- a/gnovm/tests/files/zrealm1.gno +++ b/gnovm/tests/files/zrealm1.gno @@ -170,6 +170,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm2.gno b/gnovm/tests/files/zrealm2.gno index bf321c42d31..67ba2f5a768 100644 --- a/gnovm/tests/files/zrealm2.gno +++ b/gnovm/tests/files/zrealm2.gno @@ -173,6 +173,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.3", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -207,6 +209,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm3.gno b/gnovm/tests/files/zrealm3.gno index 4ff8dd1a531..da8a581375c 100644 --- a/gnovm/tests/files/zrealm3.gno +++ b/gnovm/tests/files/zrealm3.gno @@ -172,6 +172,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.2", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -206,6 +208,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm4.gno b/gnovm/tests/files/zrealm4.gno index 2e2fa4e8d09..dc3c48c774b 100644 --- a/gnovm/tests/files/zrealm4.gno +++ b/gnovm/tests/files/zrealm4.gno @@ -114,6 +114,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -148,6 +150,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm5.gno b/gnovm/tests/files/zrealm5.gno index 8ad3e7400b3..e65b089c18d 100644 --- a/gnovm/tests/files/zrealm5.gno +++ b/gnovm/tests/files/zrealm5.gno @@ -185,6 +185,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -219,6 +221,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm6.gno b/gnovm/tests/files/zrealm6.gno index fbe320ad962..20615fa7d39 100644 --- a/gnovm/tests/files/zrealm6.gno +++ b/gnovm/tests/files/zrealm6.gno @@ -257,6 +257,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -291,6 +293,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm7.gno b/gnovm/tests/files/zrealm7.gno index 689d55d3916..9decb0dae10 100644 --- a/gnovm/tests/files/zrealm7.gno +++ b/gnovm/tests/files/zrealm7.gno @@ -329,6 +329,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -363,6 +365,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm_avl0.gno b/gnovm/tests/files/zrealm_avl0.gno index 814e19d6d49..e91788ac8eb 100644 --- a/gnovm/tests/files/zrealm_avl0.gno +++ b/gnovm/tests/files/zrealm_avl0.gno @@ -267,6 +267,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -301,6 +303,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm_avl1.gno b/gnovm/tests/files/zrealm_avl1.gno index 410e9e93601..cdd56a5ad89 100644 --- a/gnovm/tests/files/zrealm_avl1.gno +++ b/gnovm/tests/files/zrealm_avl1.gno @@ -291,6 +291,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -325,6 +327,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno new file mode 100644 index 00000000000..60e0d448202 --- /dev/null +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -0,0 +1,197 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "std" +) + +var node interface{} + +func init() { + node = std.GetOrigCaller +} + +func main() { + f := node.(func() std.Address) + println(f()) + node = std.DerivePkgAddr + g := node.(func(path string) std.Address) + println(g("x")) +} + +// Output: +// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// g19kt9e22k34ny5jf5plrjdltmws0jc0qqd2cwky + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "3", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "", +// "Line": "0", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "10", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "14", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "pkgPath", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// } +// } +// ], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "std.Address" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:5" +// }, +// "FileName": "native.gno", +// "IsMethod": false, +// "Name": "DerivePkgAddr", +// "NativeName": "DerivePkgAddr", +// "NativePkg": "std", +// "PkgPath": "std", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "native.gno", +// "Line": "15", +// "Nonce": "0", +// "PkgPath": "std" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "pkgPath", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// } +// } +// ], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "std.Address" +// } +// } +// ] +// } +// } +// } +// ] +// } diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0.gno index cfb1f08c6f4..73d07f726eb 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0.gno @@ -239,62 +239,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": true, // "Name": "Modify", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "File": "tests.gno", -// "Line": "42", -// "Nonce": "0", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "tests.gno", -// "IsMethod": true, -// "Name": "Modify", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -399,6 +345,8 @@ func main() { // "FileName": "interfaces.gno", // "IsMethod": false, // "Name": "AddStringer", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -463,6 +411,8 @@ func main() { // "FileName": "interfaces.gno", // "IsMethod": false, // "Name": "Render", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -517,6 +467,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "IncCounter", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -561,6 +513,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "Counter", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -615,6 +569,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "CurrentRealmPath", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -659,6 +615,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "AssertOriginCall", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -703,6 +661,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "IsOriginCall", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -760,6 +720,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "ModifyTestRealmObject", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -807,6 +769,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "InitTestNodes", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -841,6 +805,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "ModTestNodes", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -875,6 +841,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "PrintTestNodes", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -919,6 +887,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "GetPrevRealm", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -973,6 +943,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "GetRSubtestsPrevRealm", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -1028,6 +1000,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "Exec", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 0741d0b466a..0db5651fbcc 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -26,6 +26,7 @@ import ( "math/rand" "net" "net/url" + "os" "path/filepath" "reflect" "sort" @@ -33,17 +34,15 @@ import ( "strings" "sync" "sync/atomic" - "testing" "text/template" "time" "unicode/utf8" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" - "github.com/gnolang/gno/tm2/pkg/crypto" + teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" dbm "github.com/gnolang/gno/tm2/pkg/db" osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store/dbadapter" "github.com/gnolang/gno/tm2/pkg/store/iavl" stypes "github.com/gnolang/gno/tm2/pkg/store/types" @@ -51,15 +50,16 @@ import ( type importMode uint64 +// Import modes to control the import behaviour of TestStore. const ( + // use stdlibs/* only (except a few exceptions). for stdlibs/* and examples/* testing. ImportModeStdlibsOnly importMode = iota + // use stdlibs/* if present, otherwise use native. used in files/tests, excluded for *_native.go ImportModeStdlibsPreferred + // do not use stdlibs/* if native registered. used in files/tests, excluded for *_stdlibs.go ImportModeNativePreferred ) -// ImportModeStdlibsOnly: use stdlibs/* only (except a few exceptions). for stdlibs/* and examples/* testing. -// ImportModeStdlibsPreferred: use stdlibs/* if present, otherwise use native. for files/tests2/*. -// ImportModeNativePreferred: do not use stdlibs/* if native registered. for files/tests/*. // NOTE: this isn't safe, should only be used for testing. func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { @@ -93,23 +93,9 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - stdlibPath := filepath.Join(rootDir, "gnovm", "stdlibs", pkgPath) - if osm.DirExists(stdlibPath) { - memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) - if !memPkg.IsEmpty() { - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - // NOTE: see also pkgs/sdk/vm/builtins.go - // XXX: why does this fail when just pkgPath? - PkgPath: "gno.land/r/stdlibs/" + pkgPath, - Output: stdout, - Store: store, - }) - save := pkgPath != "testing" // never save the "testing" package - return m2.RunMemPackage(memPkg, save) - } - - // There is no package there, but maybe we have a - // native counterpart below. + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + if pn != nil { + return } } @@ -120,12 +106,10 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkgPath == "crypto/rand" || pkgPath == "crypto/md5" || pkgPath == "crypto/sha1" || - pkgPath == "encoding/base64" || pkgPath == "encoding/binary" || pkgPath == "encoding/json" || pkgPath == "encoding/xml" || pkgPath == "internal/os_test" || - pkgPath == "math" || pkgPath == "math/big" || pkgPath == "math/rand" || mode == ImportModeStdlibsPreferred || @@ -215,17 +199,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg.DefineGoNativeType(reflect.TypeOf(net.TCPAddr{})) pkg.DefineGoNativeValue("IPv4", net.IPv4) return pkg, pkg.NewPackage() - case "net/http": - // XXX UNSAFE - // There's no reason why we can't replace these with safer alternatives. - panic("just say gno") - /* - pkg := gno.NewPackageNode("http", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(http.Request{})) - pkg.DefineGoNativeValue("DefaultClient", http.DefaultClient) - pkg.DefineGoNativeType(reflect.TypeOf(http.Client{})) - return pkg, pkg.NewPackage() - */ case "net/url": pkg := gno.NewPackageNode("url", pkgPath, nil) pkg.DefineGoNativeType(reflect.TypeOf(url.Values{})) @@ -277,6 +250,8 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg.DefineGoNativeValue("Abs", math.Abs) pkg.DefineGoNativeValue("Cos", math.Cos) pkg.DefineGoNativeValue("Pi", math.Pi) + pkg.DefineGoNativeValue("Float64bits", math.Float64bits) + pkg.DefineGoNativeValue("Pi", math.Pi) pkg.DefineGoNativeValue("MaxFloat32", math.MaxFloat32) pkg.DefineGoNativeValue("MaxFloat64", math.MaxFloat64) return pkg, pkg.NewPackage() @@ -385,25 +360,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg := gno.NewPackageNode("fnv", pkgPath, nil) pkg.DefineGoNativeValue("New32a", fnv.New32a) return pkg, pkg.NewPackage() - /* XXX support somehow for speed. for now, generic implemented in stdlibs. - case "internal/bytealg": - pkg := gno.NewPackageNode("bytealg", pkgPath, nil) - pkg.DefineGoNativeValue("Compare", bytealg.Compare) - pkg.DefineGoNativeValue("CountString", bytealg.CountString) - pkg.DefineGoNativeValue("Cutover", bytealg.Cutover) - pkg.DefineGoNativeValue("Equal", bytealg.Equal) - pkg.DefineGoNativeValue("HashStr", bytealg.HashStr) - pkg.DefineGoNativeValue("HashStrBytes", bytealg.HashStrBytes) - pkg.DefineGoNativeValue("HashStrRev", bytealg.HashStrRev) - pkg.DefineGoNativeValue("HashStrRevBytes", bytealg.HashStrRevBytes) - pkg.DefineGoNativeValue("Index", bytealg.Index) - pkg.DefineGoNativeValue("IndexByte", bytealg.IndexByte) - pkg.DefineGoNativeValue("IndexByteString", bytealg.IndexByteString) - pkg.DefineGoNativeValue("IndexRabinKarp", bytealg.IndexRabinKarp) - pkg.DefineGoNativeValue("IndexRabinKarpBytes", bytealg.IndexRabinKarpBytes) - pkg.DefineGoNativeValue("IndexString", bytealg.IndexString) - return pkg, pkg.NewPackage() - */ default: // continue on... } @@ -411,19 +367,8 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - stdlibPath := filepath.Join(rootDir, "gnovm", "stdlibs", pkgPath) - if osm.DirExists(stdlibPath) { - memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) - if memPkg.IsEmpty() { - panic(fmt.Sprintf("found an empty package %q", pkgPath)) - } - - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: stdout, - Store: store, - }) - pn, pv = m2.RunMemPackage(memPkg, true) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + if pn != nil { return } } @@ -452,6 +397,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) store = gno.NewStore(nil, baseStore, iavlStore) store.SetPackageGetter(getPackage) + store.SetNativeStore(teststdlibs.NativeStore) store.SetPackageInjector(testPackageInjector) store.SetStrictGo2GnoMapping(false) // native mappings @@ -459,30 +405,49 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri return } -//---------------------------------------- -// testInjectNatives -// analogous to stdlibs.InjectNatives, but with -// native methods suitable for the testing environment. - -func testPackageInjector(store gno.Store, pn *gno.PackageNode) { - // Also inject stdlibs native functions. - stdlibs.InjectPackage(store, pn) - isOriginCall := func(m *gno.Machine) bool { - tname := m.Frames[0].Func.Name - switch tname { - case "main": // test is a _filetest - return len(m.Frames) == 3 - case "runtest": // test is a _test - return len(m.Frames) == 7 +func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gno.PackageNode, *gno.PackageValue) { + dirs := [...]string{ + // normal stdlib path. + filepath.Join(rootDir, "gnovm", "stdlibs", pkgPath), + // override path. definitions here override the previous if duplicate. + filepath.Join(rootDir, "gnovm", "tests", "stdlibs", pkgPath), + } + files := make([]string, 0, 32) // pre-alloc 32 as a likely high number of files + for _, path := range dirs { + dl, err := os.ReadDir(path) + if err != nil { + if os.IsNotExist(err) { + continue + } + panic(fmt.Errorf("could not access dir %q: %w", path, err)) } - // support init() in _filetest - // XXX do we need to distinguish from 'runtest'/_test? - // XXX pretty hacky even if not. - if strings.HasPrefix(string(tname), "init.") { - return len(m.Frames) == 3 + + for _, f := range dl { + // NOTE: RunMemPackage has other rules; those should be mostly useful + // for on-chain packages (ie. include README and gno.mod). + if !f.IsDir() && strings.HasSuffix(f.Name(), ".gno") { + files = append(files, filepath.Join(path, f.Name())) + } } - panic("unable to determine if test is a _test or a _filetest") } + if len(files) == 0 { + return nil, nil + } + + memPkg := gno.ReadMemPackageFromList(files, pkgPath) + m2 := gno.NewMachineWithOptions(gno.MachineOptions{ + // NOTE: see also pkgs/sdk/vm/builtins.go + // Needs PkgPath != its name because TestStore.getPackage is the package + // getter for the store, which calls loadStdlib, so it would be recursively called. + PkgPath: "stdlibload", + Output: stdout, + Store: store, + }) + save := pkgPath != "testing" // never save the "testing" package + return m2.RunMemPackageWithOverrides(memPkg, save) +} + +func testPackageInjector(store gno.Store, pn *gno.PackageNode) { // Test specific injections: switch pn.PkgPath { case "strconv": @@ -490,203 +455,6 @@ func testPackageInjector(store gno.Store, pn *gno.PackageNode) { // from stdlibs.InjectNatives. pn.DefineGoNativeType(reflect.TypeOf(strconv.NumError{})) pn.DefineGoNativeValue("ParseInt", strconv.ParseInt) - case "std": - // NOTE: some of these are overrides. - // Also see stdlibs/InjectPackage. - pn.DefineNativeOverride("AssertOriginCall", - /* - gno.Flds( // params - ), - gno.Flds( // results - ), - */ - func(m *gno.Machine) { - if !isOriginCall(m) { - m.Panic(typedString("invalid non-origin call")) - return - } - }, - ) - pn.DefineNativeOverride("IsOriginCall", - /* - gno.Flds( // params - ), - gno.Flds( // results - "isOrigin", "bool", - ), - */ - func(m *gno.Machine) { - res0 := gno.TypedValue{T: gno.BoolType} - res0.SetBool(isOriginCall(m)) - m.PushValue(res0) - }, - ) - pn.DefineNativeOverride("GetCallerAt", - /* - gno.Flds( // params - "n", "int", - ), - gno.Flds( // results - "", "Address", - ), - */ - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - n := arg0.GetInt() - if n <= 0 { - m.Panic(typedString("GetCallerAt requires positive arg")) - return - } - if n > m.NumFrames()-1 { - // NOTE: the last frame's LastPackage - // is set to the original non-frame - // package, so need this check. - m.Panic(typedString("frame not found")) - return - } - var pkgAddr string - if n == m.NumFrames()-1 { - // This makes it consistent with GetOrigCaller and TestSetOrigCaller. - ctx := m.Context.(stdlibs.ExecContext) - pkgAddr = string(ctx.OrigCaller) - } else { - pkgAddr = string(m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) - } - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(pkgAddr), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - pn.DefineNative("TestSetOrigCaller", - gno.Flds( // params - "", "Address", - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - addr := arg0.GetString() - // overwrite context - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigCaller = crypto.Bech32Address(addr) - m.Context = ctx - }, - ) - pn.DefineNative("TestSetOrigPkgAddr", - gno.Flds( // params - "", "Address", - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - addr := crypto.Bech32Address(arg0.GetString()) - // overwrite context - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigPkgAddr = addr - m.Context = ctx - }, - ) - pn.DefineNative("TestSetOrigSend", - gno.Flds( // params - "sent", "Coins", - "spent", "Coins", - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - arg0, arg1 := m.LastBlock().GetParams2() - var sent std.Coins - rvSent := reflect.ValueOf(&sent).Elem() - gno.Gno2GoValue(arg0.TV, rvSent) - sent = rvSent.Interface().(std.Coins) // needed? - var spent std.Coins - rvSpent := reflect.ValueOf(&spent).Elem() - gno.Gno2GoValue(arg1.TV, rvSpent) - spent = rvSpent.Interface().(std.Coins) // needed? - // overwrite context. - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigSend = sent - ctx.OrigSendSpent = &spent - m.Context = ctx - }, - ) - pn.DefineNative("TestIssueCoins", - gno.Flds( // params - "addr", "Address", - "coins", "Coins", - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - arg0, arg1 := m.LastBlock().GetParams2() - addr := crypto.Bech32Address(arg0.TV.GetString()) - var coins std.Coins - rvCoins := reflect.ValueOf(&coins).Elem() - gno.Gno2GoValue(arg1.TV, rvCoins) - coins = rvCoins.Interface().(std.Coins) // needed? - // overwrite context. - ctx := m.Context.(stdlibs.ExecContext) - banker := ctx.Banker - for _, coin := range coins { - banker.IssueCoin(addr, coin.Denom, coin.Amount) - } - }, - ) - pn.DefineNative("TestCurrentRealm", - gno.Flds( // params - ), - gno.Flds( // results - "realm", "string", - ), - func(m *gno.Machine) { - rlmpath := m.Realm.Path - m.PushValue(typedString(rlmpath)) - }, - ) - pn.DefineNative("TestSkipHeights", - gno.Flds( // params - "count", "int64", - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - count := arg0.GetInt64() - - ctx := m.Context.(stdlibs.ExecContext) - ctx.Height += count - m.Context = ctx - }, - ) - // TODO: move elsewhere. - pn.DefineNative("ClearStoreCache", - gno.Flds( // params - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - if gno.IsDebug() && testing.Verbose() { - store.Print() - fmt.Println("========================================") - fmt.Println("CLEAR CACHE (RUNTIME)") - fmt.Println("========================================") - } - m.Store.ClearCache() - m.PreprocessAllFilesAndSaveBlockNodes() - if gno.IsDebug() && testing.Verbose() { - store.Print() - fmt.Println("========================================") - fmt.Println("CLEAR CACHE DONE") - fmt.Println("========================================") - } - }, - ) } } @@ -703,13 +471,6 @@ func (*dummyReader) Read(b []byte) (n int, err error) { //---------------------------------------- -// NOTE: does not allocate; used for panics. -func typedString(s string) gno.TypedValue { - tv := gno.TypedValue{T: gno.StringType} - tv.V = gno.StringValue(s) - return tv -} - type TestReport struct { Name string Verbose bool diff --git a/gnovm/tests/stdlibs/README.md b/gnovm/tests/stdlibs/README.md new file mode 100644 index 00000000000..16d5d171342 --- /dev/null +++ b/gnovm/tests/stdlibs/README.md @@ -0,0 +1,6 @@ +# tests/stdlibs + +This directory contains test-specific standard libraries. These are only +available when testing gno code in `_test.gno` and `_filetest.gno` files. +Re-declarations of functions already existing override the definitions of the +normal stdlibs directory. diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go new file mode 100644 index 00000000000..18d281bebe0 --- /dev/null +++ b/gnovm/tests/stdlibs/native.go @@ -0,0 +1,226 @@ +// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. +// To regenerate it, run `go generate` from this directory. + +package stdlibs + +import ( + "reflect" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + testlibs_std "github.com/gnolang/gno/gnovm/tests/stdlibs/std" + tm2_crypto "github.com/gnolang/gno/tm2/pkg/crypto" + tm2_std "github.com/gnolang/gno/tm2/pkg/std" +) + +type nativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + f func(m *gno.Machine) +} + +var nativeFuncs = [...]nativeFunc{ + { + "std", + "AssertOriginCall", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + testlibs_std.AssertOriginCall( + m, + ) + }, + }, + { + "std", + "IsOriginCall", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + }, + func(m *gno.Machine) { + r0 := testlibs_std.IsOriginCall( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "TestCurrentRealm", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + r0 := testlibs_std.TestCurrentRealm( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "TestSkipHeights", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int64")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int64 + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + testlibs_std.TestSkipHeights( + m, + p0) + }, + }, + { + "std", + "ClearStoreCache", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + testlibs_std.ClearStoreCache( + m, + ) + }, + }, + { + "std", + "GetCallerAt", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := testlibs_std.GetCallerAt( + m, + p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "TestSetOrigCaller", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("Address")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 tm2_crypto.Bech32Address + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + testlibs_std.TestSetOrigCaller( + m, + p0) + }, + }, + { + "std", + "TestSetOrigPkgAddr", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("Address")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 tm2_crypto.Bech32Address + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + testlibs_std.TestSetOrigPkgAddr( + m, + p0) + }, + }, + { + "std", + "TestSetOrigSend", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("Coins")}, + {Name: gno.N("p1"), Type: gno.X("Coins")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 tm2_std.Coins + rp0 = reflect.ValueOf(&p0).Elem() + p1 tm2_std.Coins + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + testlibs_std.TestSetOrigSend( + m, + p0, p1) + }, + }, + { + "std", + "TestIssueCoins", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p1"), Type: gno.X("Coins")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 tm2_crypto.Bech32Address + rp0 = reflect.ValueOf(&p0).Elem() + p1 tm2_std.Coins + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + testlibs_std.TestIssueCoins( + m, + p0, p1) + }, + }, +} diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno new file mode 100644 index 00000000000..380549be694 --- /dev/null +++ b/gnovm/tests/stdlibs/std/std.gno @@ -0,0 +1,12 @@ +package std + +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func TestCurrentRealm() string // injected +func TestSkipHeights(count int64) // injected +func ClearStoreCache() // injected +func GetCallerAt(n int) Address // injected +func TestSetOrigCaller(addr Address) // injected +func TestSetOrigPkgAddr(addr Address) // injected +func TestSetOrigSend(sent, spent Coins) // injected +func TestIssueCoins(addr Address, coins Coins) // injected diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go new file mode 100644 index 00000000000..27ad079b4a6 --- /dev/null +++ b/gnovm/tests/stdlibs/std/std.go @@ -0,0 +1,116 @@ +package std + +import ( + "fmt" + "strings" + "testing" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs" + "github.com/gnolang/gno/gnovm/stdlibs/std" + "github.com/gnolang/gno/tm2/pkg/crypto" + tm2std "github.com/gnolang/gno/tm2/pkg/std" +) + +func AssertOriginCall(m *gno.Machine) { + if !IsOriginCall(m) { + m.Panic(typedString("invalid non-origin call")) + } +} + +func typedString(s gno.StringValue) gno.TypedValue { + tv := gno.TypedValue{T: gno.StringType} + tv.SetString(s) + return tv +} + +func IsOriginCall(m *gno.Machine) bool { + tname := m.Frames[0].Func.Name + switch tname { + case "main": // test is a _filetest + return len(m.Frames) == 3 + case "runtest": // test is a _test + return len(m.Frames) == 7 + } + // support init() in _filetest + // XXX do we need to distinguish from 'runtest'/_test? + // XXX pretty hacky even if not. + if strings.HasPrefix(string(tname), "init.") { + return len(m.Frames) == 3 + } + panic("unable to determine if test is a _test or a _filetest") +} + +func TestCurrentRealm(m *gno.Machine) string { + return m.Realm.Path +} + +func TestSkipHeights(m *gno.Machine, count int64) { + ctx := m.Context.(std.ExecContext) + ctx.Height += count + m.Context = ctx +} + +func ClearStoreCache(m *gno.Machine) { + if gno.IsDebug() && testing.Verbose() { + m.Store.Print() + fmt.Println("========================================") + fmt.Println("CLEAR CACHE (RUNTIME)") + fmt.Println("========================================") + } + m.Store.ClearCache() + m.PreprocessAllFilesAndSaveBlockNodes() + if gno.IsDebug() && testing.Verbose() { + m.Store.Print() + fmt.Println("========================================") + fmt.Println("CLEAR CACHE DONE") + fmt.Println("========================================") + } +} + +func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { + if n <= 0 { + m.Panic(typedString("GetCallerAt requires positive arg")) + return "" + } + if n > m.NumFrames()-1 { + // NOTE: the last frame's LastPackage + // is set to the original non-frame + // package, so need this check. + m.Panic(typedString("frame not found")) + return "" + } + if n == m.NumFrames()-1 { + // This makes it consistent with GetOrigCaller and TestSetOrigCaller. + ctx := m.Context.(stdlibs.ExecContext) + return ctx.OrigCaller + } + return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() +} + +func TestSetOrigCaller(m *gno.Machine, addr crypto.Bech32Address) { + ctx := m.Context.(std.ExecContext) + ctx.OrigCaller = addr + m.Context = ctx +} + +func TestSetOrigPkgAddr(m *gno.Machine, addr crypto.Bech32Address) { + ctx := m.Context.(stdlibs.ExecContext) + ctx.OrigPkgAddr = addr + m.Context = ctx +} + +func TestSetOrigSend(m *gno.Machine, sent, spent tm2std.Coins) { + ctx := m.Context.(stdlibs.ExecContext) + ctx.OrigSend = sent + ctx.OrigSendSpent = &spent + m.Context = ctx +} + +func TestIssueCoins(m *gno.Machine, addr crypto.Bech32Address, coins tm2std.Coins) { + ctx := m.Context.(stdlibs.ExecContext) + banker := ctx.Banker + for _, coin := range coins { + banker.IssueCoin(addr, coin.Denom, coin.Amount) + } +} diff --git a/gnovm/tests/stdlibs/stdlibs.go b/gnovm/tests/stdlibs/stdlibs.go new file mode 100644 index 00000000000..b0a1050af41 --- /dev/null +++ b/gnovm/tests/stdlibs/stdlibs.go @@ -0,0 +1,18 @@ +// Package stdlibs provides supplemental stdlibs for the testing environment. +package stdlibs + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs" +) + +//go:generate go run github.com/gnolang/gno/misc/genstd + +func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { + for _, nf := range nativeFuncs { + if nf.gnoPkg == pkgPath && name == nf.gnoFunc { + return nf.f + } + } + return stdlibs.NativeStore(pkgPath, name) +} diff --git a/go.mod b/go.mod index f4317389c49..599396eee99 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/btcsuite/btcd/btcutil v1.1.3 - github.com/cockroachdb/apd v1.1.0 + github.com/cockroachdb/apd/v3 v3.2.1 github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/dgraph-io/badger/v3 v3.2103.4 @@ -60,7 +60,6 @@ require ( github.com/gorilla/sessions v1.2.1 // indirect github.com/klauspost/compress v1.12.3 // indirect github.com/kr/text v0.2.0 // indirect - github.com/lib/pq v1.10.7 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 8569f1e443b..4a4dbed28d6 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf 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/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -144,7 +144,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/linxGnu/grocksdb v1.8.5 h1:Okfk5B1h0ikCYdDM7Tc5yJUS8LTwAmMBq5IPWTmOLPs= diff --git a/misc/Makefile b/misc/Makefile new file mode 100644 index 00000000000..84acc40e387 --- /dev/null +++ b/misc/Makefile @@ -0,0 +1,28 @@ +.PHONY: help +help: + @echo "Available make commands:" + @cat Makefile | grep '^[a-z][^:]*:' | cut -d: -f1 | sort | sed 's/^/ /' + +rundep=go run -modfile ./devdeps/go.mod + +######################################## +# Dev tools +.PHONY: lint +lint: + $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint run --config ../.github/golangci.yml ./... + +.PHONY: fmt +GOFMT_FLAGS ?= -w +fmt: + $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) . + +######################################## +# Test suite +.PHONY: test +test: _test.genstd + +GOTEST_FLAGS ?= -v -p 1 -timeout=30m + +.PHONY: _test.genstd +_test.genstd: + go test ./genstd/... $(GOTEST_FLAGS) diff --git a/misc/deployments/staging.gno.land/Makefile b/misc/deployments/staging.gno.land/Makefile index 63966df6ec1..516be6dfc0e 100644 --- a/misc/deployments/staging.gno.land/Makefile +++ b/misc/deployments/staging.gno.land/Makefile @@ -11,7 +11,7 @@ logs: down: docker compose down docker volume rm -f staginggnoland_gnonode - docker compose run gnoland rm -rf /opt/gno/src/testdir/data /opt/gno/src/testdir/config + docker compose run gnoland rm -rf /opt/gno/src/gno.land/testdir/data /opt/gno/src/gno.land/testdir/config pull: git pull diff --git a/misc/deployments/staging.gno.land/docker-compose.yml b/misc/deployments/staging.gno.land/docker-compose.yml index af5e747e653..bf825b54536 100644 --- a/misc/deployments/staging.gno.land/docker-compose.yml +++ b/misc/deployments/staging.gno.land/docker-compose.yml @@ -5,6 +5,9 @@ services: container_name: gnoland build: ../../.. environment: + - VIRTUAL_HOST=rpc.staging.gno.land + - VIRTUAL_PORT=36657 + - LETSENCRYPT_HOST=rpc.staging.gno.land - LOG_LEVEL=4 working_dir: /opt/gno/src/gno.land command: @@ -36,7 +39,6 @@ services: - --faucet-url=https://faucet-staging.gno.land/ - --help-chainid=staging - --help-remote=staging.gno.land:36657 - - --views-dir=./gno.land/cmd/gnoweb/views - --with-analytics volumes: - "./overlay:/overlay:ro" diff --git a/misc/deployments/staging.gno.land/overlay/config.toml b/misc/deployments/staging.gno.land/overlay/config.toml index a02bb45bbb7..1f5952f9638 100644 --- a/misc/deployments/staging.gno.land/overlay/config.toml +++ b/misc/deployments/staging.gno.land/overlay/config.toml @@ -123,7 +123,7 @@ max_body_bytes = 1000000 max_header_bytes = 1048576 # The path to a file containing certificate that is used to create the HTTPS server. -# Migth be either absolute path or path related to tendermint's config directory. +# Might be either absolute path or path related to tendermint's config directory. # If the certificate is signed by a certificate authority, # the certFile should be the concatenation of the server's certificate, any intermediates, # and the CA's certificate. @@ -131,7 +131,7 @@ max_header_bytes = 1048576 tls_cert_file = "" # The path to a file containing matching private key that is used to create the HTTPS server. -# Migth be either absolute path or path related to tendermint's config directory. +# Might be either absolute path or path related to tendermint's config directory. # NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. tls_key_file = "" diff --git a/misc/deployments/test2.gno.land/overlay/config.toml b/misc/deployments/test2.gno.land/overlay/config.toml index edf04ba4431..65e34a2c55c 100644 --- a/misc/deployments/test2.gno.land/overlay/config.toml +++ b/misc/deployments/test2.gno.land/overlay/config.toml @@ -123,7 +123,7 @@ max_body_bytes = 1000000 max_header_bytes = 1048576 # The path to a file containing certificate that is used to create the HTTPS server. -# Migth be either absolute path or path related to tendermint's config directory. +# Might be either absolute path or path related to tendermint's config directory. # If the certificate is signed by a certificate authority, # the certFile should be the concatenation of the server's certificate, any intermediates, # and the CA's certificate. @@ -131,7 +131,7 @@ max_header_bytes = 1048576 tls_cert_file = "" # The path to a file containing matching private key that is used to create the HTTPS server. -# Migth be either absolute path or path related to tendermint's config directory. +# Might be either absolute path or path related to tendermint's config directory. # NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. tls_key_file = "" diff --git a/misc/deployments/test3.gno.land/overlay/config.toml b/misc/deployments/test3.gno.land/overlay/config.toml index edf04ba4431..65e34a2c55c 100644 --- a/misc/deployments/test3.gno.land/overlay/config.toml +++ b/misc/deployments/test3.gno.land/overlay/config.toml @@ -123,7 +123,7 @@ max_body_bytes = 1000000 max_header_bytes = 1048576 # The path to a file containing certificate that is used to create the HTTPS server. -# Migth be either absolute path or path related to tendermint's config directory. +# Might be either absolute path or path related to tendermint's config directory. # If the certificate is signed by a certificate authority, # the certFile should be the concatenation of the server's certificate, any intermediates, # and the CA's certificate. @@ -131,7 +131,7 @@ max_header_bytes = 1048576 tls_cert_file = "" # The path to a file containing matching private key that is used to create the HTTPS server. -# Migth be either absolute path or path related to tendermint's config directory. +# Might be either absolute path or path related to tendermint's config directory. # NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. tls_key_file = "" diff --git a/misc/docusaurus/sidebars.js b/misc/docusaurus/sidebars.js index 265bfb49f9c..64891fb0d96 100644 --- a/misc/docusaurus/sidebars.js +++ b/misc/docusaurus/sidebars.js @@ -36,6 +36,32 @@ const sidebars = { 'how-to-guides/connect-wallet-dapp', ], }, + { + type: 'category', + label: 'Concepts', + items: [ + 'concepts/realms', + 'concepts/packages', + 'concepts/tendermint2', + 'concepts/gnovm', + 'concepts/proof-of-contribution', + 'concepts/gno-language', + 'concepts/gno-modules', + 'concepts/gno-test', + 'concepts/from-go-to-gno', + ], + }, + { + type: 'category', + label: 'Gno Tooling', + items: [ + 'concepts/gno-tooling/cli/gno-tooling-gno', + 'concepts/gno-tooling/cli/gno-tooling-gnokey', + 'concepts/gno-tooling/cli/gno-tooling-gnofaucet', + 'concepts/gno-tooling/cli/gno-tooling-gnoland', + 'concepts/gno-tooling/cli/gno-tooling-tm2txsync', + ] + }, { type: 'category', label: 'Reference', @@ -81,31 +107,6 @@ const sidebars = { }, ], }, - { - 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', - ] - }, - ], - }, ], }; diff --git a/misc/docusaurus/src/css/custom.css b/misc/docusaurus/src/css/custom.css index 58afeea4a35..feee9badf9d 100644 --- a/misc/docusaurus/src/css/custom.css +++ b/misc/docusaurus/src/css/custom.css @@ -35,6 +35,8 @@ --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); + + --content-max-w: 50rem; } /* For readability concerns, you should choose a lighter palette in dark mode. */ @@ -61,7 +63,7 @@ body { margin-left: auto; margin-right: auto; - max-width: 84rem; + max-width: 100rem; padding-inline: 1rem; } @@ -114,6 +116,10 @@ pre code { } } +.theme-doc-markdown { + max-width: var(--content-max-w, 700px); +} + .navbar-sidebar--show ~ .main-wrapper { filter: blur(2px); } @@ -126,6 +132,7 @@ pre code { .pagination-nav { grid-template-columns: repeat(1, 100%); grid-template-rows: repeat(2, 1fr); + max-width: var(--content-max-w, 700px); } .pagination-nav__link--next { grid-column: 1/2; diff --git a/misc/genstd/exprstring.go b/misc/genstd/exprstring.go new file mode 100644 index 00000000000..c95c05c584e --- /dev/null +++ b/misc/genstd/exprstring.go @@ -0,0 +1,290 @@ +// Forked from go/types (go 1.20.3) to implement support for *linkedIdent. +// It cannot be easily split from the original as WriteExpr is highly recursive. + +// 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. + +// This file implements printing of expressions. + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/types" +) + +const ( + printerModeGoQualified = iota + printerModeGnoType +) + +type exprPrinter struct { + mode int +} + +// ExprString returns the (possibly shortened) string representation for x. +// Shortened representations are suitable for user interfaces but may not +// necessarily follow Go syntax. +// +// ExprString is identical to [types.ExprString] with the difference that it +// supports *linkedIdent. +func (ep *exprPrinter) ExprString(x ast.Expr) string { + var buf bytes.Buffer + ep.WriteExpr(&buf, x) + return buf.String() +} + +// WriteExpr writes the (possibly shortened) string representation for x to buf. +// Shortened representations are suitable for user interfaces but may not +// necessarily follow Go syntax. +// +// WriteExpr is identical to [types.WriteExpr] with the difference that it +// supports *linkedIdent. +func (ep *exprPrinter) WriteExpr(buf *bytes.Buffer, x ast.Expr) { + // The AST preserves source-level parentheses so there is + // no need to introduce them here to correct for different + // operator precedences. (This assumes that the AST was + // generated by a Go parser.) + + switch x := x.(type) { + default: + // fallback to go original -- for all non-recursive ast.Expr types + types.WriteExpr(buf, x) + + case *linkedIdent: + switch ep.mode { + case printerModeGoQualified: + n := pkgNameFromPath(x.lt.goPackage) + buf.WriteString(n) + buf.WriteByte('.') + buf.WriteString(x.lt.goName) + case printerModeGnoType: + buf.WriteString(x.lt.gnoName) + default: + panic(fmt.Errorf("invalid mode %d", ep.mode)) + } + + case *ast.Ellipsis: + buf.WriteString("...") + if x.Elt != nil { + ep.WriteExpr(buf, x.Elt) + } + + case *ast.FuncLit: + buf.WriteByte('(') + ep.WriteExpr(buf, x.Type) + buf.WriteString(" literal)") // shortened + + case *ast.CompositeLit: + ep.WriteExpr(buf, x.Type) + buf.WriteByte('{') + if len(x.Elts) > 0 { + buf.WriteString("…") + } + buf.WriteByte('}') + + case *ast.ParenExpr: + buf.WriteByte('(') + ep.WriteExpr(buf, x.X) + buf.WriteByte(')') + + case *ast.SelectorExpr: + ep.WriteExpr(buf, x.X) + buf.WriteByte('.') + buf.WriteString(x.Sel.Name) + + case *ast.IndexExpr, *ast.IndexListExpr: + ix := tpUnpackIndexExpr(x) + ep.WriteExpr(buf, ix.X) + buf.WriteByte('[') + ep.writeExprList(buf, ix.Indices) + buf.WriteByte(']') + + case *ast.SliceExpr: + ep.WriteExpr(buf, x.X) + buf.WriteByte('[') + if x.Low != nil { + ep.WriteExpr(buf, x.Low) + } + buf.WriteByte(':') + if x.High != nil { + ep.WriteExpr(buf, x.High) + } + if x.Slice3 { + buf.WriteByte(':') + if x.Max != nil { + ep.WriteExpr(buf, x.Max) + } + } + buf.WriteByte(']') + + case *ast.TypeAssertExpr: + ep.WriteExpr(buf, x.X) + buf.WriteString(".(") + ep.WriteExpr(buf, x.Type) + buf.WriteByte(')') + + case *ast.CallExpr: + ep.WriteExpr(buf, x.Fun) + buf.WriteByte('(') + ep.writeExprList(buf, x.Args) + if x.Ellipsis.IsValid() { + buf.WriteString("...") + } + buf.WriteByte(')') + + case *ast.StarExpr: + buf.WriteByte('*') + ep.WriteExpr(buf, x.X) + + case *ast.UnaryExpr: + buf.WriteString(x.Op.String()) + ep.WriteExpr(buf, x.X) + + case *ast.BinaryExpr: + ep.WriteExpr(buf, x.X) + buf.WriteByte(' ') + buf.WriteString(x.Op.String()) + buf.WriteByte(' ') + ep.WriteExpr(buf, x.Y) + + case *ast.ArrayType: + buf.WriteByte('[') + if x.Len != nil { + ep.WriteExpr(buf, x.Len) + } + buf.WriteByte(']') + ep.WriteExpr(buf, x.Elt) + + case *ast.StructType: + buf.WriteString("struct{") + ep.writeFieldList(buf, x.Fields.List, "; ", false) + buf.WriteByte('}') + + case *ast.FuncType: + buf.WriteString("func") + ep.writeSigExpr(buf, x) + + case *ast.InterfaceType: + buf.WriteString("interface{") + ep.writeFieldList(buf, x.Methods.List, "; ", true) + buf.WriteByte('}') + + case *ast.MapType: + buf.WriteString("map[") + ep.WriteExpr(buf, x.Key) + buf.WriteByte(']') + ep.WriteExpr(buf, x.Value) + + case *ast.ChanType: + var s string + switch x.Dir { + case ast.SEND: + s = "chan<- " + case ast.RECV: + s = "<-chan " + default: + s = "chan " + } + buf.WriteString(s) + ep.WriteExpr(buf, x.Value) + } +} + +func (ep *exprPrinter) writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { + buf.WriteByte('(') + ep.writeFieldList(buf, sig.Params.List, ", ", false) + buf.WriteByte(')') + + res := sig.Results + n := res.NumFields() + if n == 0 { + // no result + return + } + + buf.WriteByte(' ') + if n == 1 && len(res.List[0].Names) == 0 { + // single unnamed result + ep.WriteExpr(buf, res.List[0].Type) + return + } + + // multiple or named result(s) + buf.WriteByte('(') + ep.writeFieldList(buf, res.List, ", ", false) + buf.WriteByte(')') +} + +func (ep *exprPrinter) writeFieldList(buf *bytes.Buffer, list []*ast.Field, sep string, iface bool) { + for i, f := range list { + if i > 0 { + buf.WriteString(sep) + } + + // field list names + ep.writeIdentList(buf, f.Names) + + // types of interface methods consist of signatures only + if sig, _ := f.Type.(*ast.FuncType); sig != nil && iface { + ep.writeSigExpr(buf, sig) + continue + } + + // named fields are separated with a blank from the field type + if len(f.Names) > 0 { + buf.WriteByte(' ') + } + + ep.WriteExpr(buf, f.Type) + + // ignore tag + } +} + +func (ep *exprPrinter) writeIdentList(buf *bytes.Buffer, list []*ast.Ident) { + for i, x := range list { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(x.Name) + } +} + +func (ep *exprPrinter) writeExprList(buf *bytes.Buffer, list []ast.Expr) { + for i, x := range list { + if i > 0 { + buf.WriteString(", ") + } + ep.WriteExpr(buf, x) + } +} + +// The following are copied from go/internal/typeparams. +// We cannot use the original directly as it comes from an "internal" package. + +// tpIndexExpr wraps an ast.IndexExpr or ast.IndexListExpr. +// +// Orig holds the original ast.Expr from which this IndexExpr was derived. +type tpIndexExpr struct { + Orig ast.Expr // the wrapped expr, which may be distinct from the IndexListExpr below. + *ast.IndexListExpr +} + +func tpUnpackIndexExpr(n ast.Node) *tpIndexExpr { + switch e := n.(type) { + case *ast.IndexExpr: + return &tpIndexExpr{e, &ast.IndexListExpr{ + X: e.X, + Lbrack: e.Lbrack, + Indices: []ast.Expr{e.Index}, + Rbrack: e.Rbrack, + }} + case *ast.IndexListExpr: + return &tpIndexExpr{e, e} + } + return nil +} diff --git a/misc/genstd/genstd.go b/misc/genstd/genstd.go new file mode 100644 index 00000000000..318a63e5ee8 --- /dev/null +++ b/misc/genstd/genstd.go @@ -0,0 +1,210 @@ +// Command genstd provides static code generation for standard library native +// bindings. +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/fs" + "os" + "path/filepath" + "strconv" + "strings" + "text/template" + + _ "embed" +) + +func main() { + path := "." + if len(os.Args) > 1 { + path = os.Args[1] + } + if err := _main(path); err != nil { + fmt.Fprintf(os.Stderr, "%+v\n", err) + os.Exit(1) + } +} + +func _main(stdlibsPath string) error { + stdlibsPath = filepath.Clean(stdlibsPath) + if s, err := os.Stat(stdlibsPath); err != nil { + return err + } else if !s.IsDir() { + return fmt.Errorf("not a directory: %q", stdlibsPath) + } + + // Gather data about each package, getting functions of interest + // (gno bodyless + go exported). + pkgs, err := walkStdlibs(stdlibsPath) + if err != nil { + return err + } + + // Link up each Gno function with its matching Go function. + mappings := linkFunctions(pkgs) + + // Create generated file. + f, err := os.Create("native.go") + if err != nil { + return fmt.Errorf("create native.go: %w", err) + } + defer f.Close() + + // Execute template. + td := &tplData{ + Mappings: mappings, + } + if err := tpl.Execute(f, td); err != nil { + return fmt.Errorf("execute template: %w", err) + } + if err := f.Close(); err != nil { + return err + } + + // gofumpt doesn't do "import fixing" like goimports: + // https://github.com/mvdan/gofumpt#frequently-asked-questions + if err := runTool("golang.org/x/tools/cmd/goimports"); err != nil { + return err + } + return runTool("mvdan.cc/gofumpt") +} + +type pkgData struct { + importPath string + fsDir string + gnoBodyless []funcDecl + goExported []funcDecl +} + +type funcDecl struct { + *ast.FuncDecl + imports []*ast.ImportSpec +} + +func addImports(fds []*ast.FuncDecl, imports []*ast.ImportSpec) []funcDecl { + r := make([]funcDecl, len(fds)) + for i, fd := range fds { + r[i] = funcDecl{fd, imports} + } + return r +} + +// walkStdlibs does a BFS walk through the given directory, expected to be a +// "stdlib" directory, parsing and keeping track of Go and Gno functions of +// interest. +func walkStdlibs(stdlibsPath string) ([]*pkgData, error) { + pkgs := make([]*pkgData, 0, 64) + err := filepath.WalkDir(stdlibsPath, func(fpath string, d fs.DirEntry, err error) error { + // skip dirs and top-level directory. + if d.IsDir() || filepath.Dir(fpath) == stdlibsPath { + return nil + } + + // skip non-source and test files. + ext := filepath.Ext(fpath) + noExt := fpath[:len(fpath)-len(ext)] + if (ext != ".go" && ext != ".gno") || strings.HasSuffix(noExt, "_test") { + return nil + } + + dir := filepath.Dir(fpath) + var pkg *pkgData + // because of bfs, we know that if we've already been in this directory + // in a previous file, it must be in the last entry of pkgs. + if len(pkgs) == 0 || pkgs[len(pkgs)-1].fsDir != dir { + pkg = &pkgData{ + importPath: strings.ReplaceAll(strings.TrimPrefix(dir, stdlibsPath+"/"), string(filepath.Separator), "/"), + fsDir: dir, + } + pkgs = append(pkgs, pkg) + } else { + pkg = pkgs[len(pkgs)-1] + } + fs := token.NewFileSet() + f, err := parser.ParseFile(fs, fpath, nil, parser.SkipObjectResolution) + if err != nil { + return err + } + if ext == ".go" { + // keep track of exported function declarations. + // warn about all exported type, const and var declarations. + if exp := filterExported(f); len(exp) > 0 { + pkg.goExported = append(pkg.goExported, addImports(exp, f.Imports)...) + } + } else if bd := filterBodylessFuncDecls(f); len(bd) > 0 { + // gno file -- keep track of function declarations without body. + pkg.gnoBodyless = append(pkg.gnoBodyless, addImports(bd, f.Imports)...) + } + return nil + }) + return pkgs, err +} + +// filterBodylessFuncDecls returns the function declarations in the given file +// which don't contain a body. +func filterBodylessFuncDecls(f *ast.File) (bodyless []*ast.FuncDecl) { + for _, decl := range f.Decls { + fd, ok := decl.(*ast.FuncDecl) + if !ok || fd.Body != nil { + continue + } + bodyless = append(bodyless, fd) + } + return +} + +// filterExported returns the exported function declarations of the given file. +func filterExported(f *ast.File) (exported []*ast.FuncDecl) { + for _, decl := range f.Decls { + switch d := decl.(type) { + case *ast.GenDecl: + // TODO: complain if there are exported types/vars/consts + continue + case *ast.FuncDecl: + if d.Name.IsExported() { + exported = append(exported, d) + } + } + } + return +} + +//go:embed template.tmpl +var templateText string + +var tpl = template.Must(template.New("").Parse(templateText)) + +// tplData is the data passed to the template. +type tplData struct { + Mappings []mapping +} + +type tplImport struct{ Name, Path string } + +func (t tplData) Imports() (res []tplImport) { + add := func(path string) { + for _, v := range res { + if v.Path == path { + return + } + } + res = append(res, tplImport{Name: pkgNameFromPath(path), Path: path}) + } + for _, m := range t.Mappings { + add(m.GoImportPath) + // There might be a bit more than we need - but we run goimports to fix that. + for _, v := range m.goImports { + s, err := strconv.Unquote(v.Path.Value) + if err != nil { + panic(fmt.Errorf("could not unquote go import string literal: %s", v.Path.Value)) + } + add(s) + } + } + return +} + +func (tplData) PkgName(path string) string { return pkgNameFromPath(path) } diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go new file mode 100644 index 00000000000..0e4034a1ab9 --- /dev/null +++ b/misc/genstd/mapping.go @@ -0,0 +1,471 @@ +package main + +import ( + "errors" + "fmt" + "go/ast" + "path" + "strconv" +) + +const gnoPackagePath = "github.com/gnolang/gno/gnovm/pkg/gnolang" + +type mapping struct { + GnoImportPath string // time + GnoFunc string // now + GoImportPath string // github.com/gnolang/gno/gnovm/stdlibs/time + GoFunc string // X_now + Params []mappingType + Results []mappingType + MachineParam bool + + gnoImports []*ast.ImportSpec + goImports []*ast.ImportSpec +} + +type mappingType struct { + // type of ast.Expr is from the normal ast.Expr types + // + *linkedIdent. + Type ast.Expr + + // IsTypedValue is set to true if the parameter or result in go is of type + // gno.TypedValue. This prevents the generated code from performing + // Go2Gno/Gno2Go reflection-based conversion. + IsTypedValue bool +} + +func (mt mappingType) GoQualifiedName() string { + return (&exprPrinter{ + mode: printerModeGoQualified, + }).ExprString(mt.Type) +} + +func (mt mappingType) GnoType() string { + return (&exprPrinter{ + mode: printerModeGnoType, + }).ExprString(mt.Type) +} + +type linkedIdent struct { + ast.BadExpr // Unused, but it makes *linkedIdent implement ast.Expr + + lt linkedType +} + +func linkFunctions(pkgs []*pkgData) []mapping { + var mappings []mapping + for _, pkg := range pkgs { + for _, gb := range pkg.gnoBodyless { + nameWant := gb.Name.Name + if !gb.Name.IsExported() { + nameWant = "X_" + nameWant + } + fn := findFuncByName(pkg.goExported, nameWant) + if fn.FuncDecl == nil { + panic( + fmt.Errorf("package %q: no matching go function declaration (%q) exists for function %q", + pkg.importPath, nameWant, gb.Name.Name), + ) + } + mp := mapping{ + GnoImportPath: pkg.importPath, + GnoFunc: gb.Name.Name, + GoImportPath: "github.com/gnolang/gno/" + relPath() + "/" + pkg.importPath, + GoFunc: fn.Name.Name, + + gnoImports: gb.imports, + goImports: fn.imports, + } + if !mp.signaturesMatch(gb, fn) { + panic( + fmt.Errorf("package %q: signature of gno function %s doesn't match signature of go function %s", + pkg.importPath, gb.Name.Name, fn.Name.Name), + ) + } + mp.loadParamsResults(gb, fn) + mappings = append(mappings, mp) + } + } + return mappings +} + +func findFuncByName(fns []funcDecl, name string) funcDecl { + for _, fn := range fns { + if fn.Name.Name == name { + return fn + } + } + return funcDecl{} +} + +func (m *mapping) loadParamsResults(gnof, gof funcDecl) { + // initialise with lengths + m.Params = make([]mappingType, 0, gnof.Type.Params.NumFields()) + m.Results = make([]mappingType, 0, gnof.Type.Results.NumFields()) + + gofpl := gof.Type.Params.List + if m.MachineParam { + // skip machine parameter + gofpl = gofpl[1:] + } + if gnof.Type.Params != nil { + m._loadParamsResults(&m.Params, gnof.Type.Params.List, gofpl) + } + if gnof.Type.Results != nil { + m._loadParamsResults(&m.Results, gnof.Type.Results.List, gof.Type.Results.List) + } +} + +func (m *mapping) _loadParamsResults(dst *[]mappingType, gnol, gol []*ast.Field) { + iterFields(gnol, gol, func(gnoe, goe ast.Expr) error { + if m.isTypedValue(goe) { + *dst = append(*dst, mappingType{Type: gnoe, IsTypedValue: true}) + } else { + merged := m.mergeTypes(gnoe, goe) + *dst = append(*dst, mappingType{Type: merged}) + } + return nil + }) +} + +// isGnoMachine checks whether field is of type *gno.Machine, +// and it has at most 1 name. +func (m *mapping) isGnoMachine(field *ast.Field) bool { + if len(field.Names) > 1 { + return false + } + + return m.isGnoType(field.Type, true, "Machine") +} + +// isTypedValue checks whether e is type gno.TypedValue. +func (m *mapping) isTypedValue(e ast.Expr) bool { + return m.isGnoType(e, false, "TypedValue") +} + +func (m *mapping) isGnoType(e ast.Expr, star bool, typeName string) bool { + if star { + px, ok := e.(*ast.StarExpr) + if !ok { + return false + } + e = px.X + } + + sx, ok := e.(*ast.SelectorExpr) + if !ok { + return false + } + + imp := resolveSelectorImport(m.goImports, sx) + return imp == gnoPackagePath && sx.Sel.Name == typeName +} + +// iterFields iterates over gnol and gol, calling callback for each matching +// parameter. iterFields assumes the caller already checked for the "true" number +// of parameters in the two arrays to be equal (can be checked using +// (*ast.FieldList).NumFields()). +// +// If callback returns an error, iterFields returns that error immediately. +// No errors are otherwise generated. +func iterFields(gnol, gol []*ast.Field, callback func(gnoType, goType ast.Expr) error) error { + var goIdx, goNameIdx int + + for _, l := range gnol { + n := len(l.Names) + if n == 0 { + n = 1 + } + gnoe := l.Type + for i := 0; i < n; i++ { + goe := gol[goIdx].Type + + if err := callback(gnoe, goe); err != nil { + return err + } + + goNameIdx++ + if goNameIdx >= len(gol[goIdx].Names) { + goIdx++ + goNameIdx = 0 + } + } + } + return nil +} + +// mergeTypes merges gnoe and goe into a single ast.Expr. +// +// gnoe and goe are expected to have the same underlying structure, but they +// may differ in their type identifiers (possibly qualified, ie pkg.T). +// if they differ, mergeTypes returns nil. +// +// When two type identifiers are found, they are checked against the list of +// linkedTypes to determine if they refer to a linkedType. If they are not, +// mergeTypes returns nil. If they are, the *ast.Ident/*ast.SelectorExpr is +// replaced with a *linkedIdent. +// +// mergeTypes does not modify the given gnoe or goe; the returned ast.Expr is +// (recursively) newly allocated. +func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { + resolveGoNamed := func(lt *linkedType) bool { + switch goe := goe.(type) { + case *ast.SelectorExpr: + // selector - resolve pkg ident to path + lt.goPackage = resolveSelectorImport(m.goImports, goe) + lt.goName = goe.Sel.Name + case *ast.Ident: + // local name -- use import path of go pkg + lt.goPackage = m.GoImportPath + lt.goName = goe.Name + default: + return false + } + return true + } + + switch gnoe := gnoe.(type) { + // We're working with a subset of all expressions: + // https://go.dev/ref/spec#Type + + case *ast.SelectorExpr: + lt := linkedType{ + gnoPackage: resolveSelectorImport(m.gnoImports, gnoe), + gnoName: gnoe.Sel.Name, + } + if !resolveGoNamed(<) || !linkedTypeExists(lt) { + return nil + } + return &linkedIdent{lt: lt} + case *ast.Ident: + // easy case - built-in identifiers + goi, ok := goe.(*ast.Ident) + if ok && isBuiltin(gnoe.Name) && gnoe.Name == goi.Name { + return &ast.Ident{Name: gnoe.Name} + } + + lt := linkedType{ + gnoPackage: m.GnoImportPath, + gnoName: gnoe.Name, + } + if !resolveGoNamed(<) || !linkedTypeExists(lt) { + return nil + } + return &linkedIdent{lt: lt} + + // easier cases -- check for equality of structure and underlying types + case *ast.StarExpr: + goe, ok := goe.(*ast.StarExpr) + if !ok { + return nil + } + x := m.mergeTypes(gnoe.X, goe.X) + if x == nil { + return nil + } + return &ast.StarExpr{X: x} + case *ast.ArrayType: + goe, ok := goe.(*ast.ArrayType) + if !ok || !basicLitsEqual(gnoe.Len, goe.Len) { + return nil + } + elt := m.mergeTypes(gnoe.Elt, goe.Elt) + if elt == nil { + return nil + } + var l ast.Expr + if gnoe.Len != nil { + l = &ast.BasicLit{Value: gnoe.Len.(*ast.BasicLit).Value} + } + return &ast.ArrayType{Len: l, Elt: elt} + + case *ast.StructType, + *ast.FuncType, + *ast.InterfaceType, + *ast.MapType, + *ast.Ellipsis: + // TODO + panic("not implemented") + default: + panic(fmt.Errorf("invalid expression as func param/return type: %T (%v)", gnoe, gnoe)) + } +} + +// returns full import path from package ident +func resolveImport(imports []*ast.ImportSpec, ident string) string { + for _, i := range imports { + s, err := strconv.Unquote(i.Path.Value) + if err != nil { + panic(fmt.Errorf("could not unquote import path literal: %s", i.Path.Value)) + } + + // TODO: for simplicity, if i.Name is nil we assume the name to be == + // to the last part of the import path. + // ideally, use importer to resolve package directory on user's FS and + // resolve by parsing and reading package clause + var name string + if i.Name != nil { + name = i.Name.Name + } else { + name = path.Base(s) + } + + if name == ident { + return s + } + } + return "" +} + +func resolveSelectorImport(imports []*ast.ImportSpec, sx *ast.SelectorExpr) string { + pkgIdent, ok := sx.X.(*ast.Ident) + if !ok { + panic(fmt.Errorf("encountered unhandled SelectorExpr.X type: %T (%v)", sx.X, sx)) + } + impPath := resolveImport(imports, pkgIdent.Name) + if impPath == "" { + panic(fmt.Errorf( + "unknown identifier %q (for resolving type %q)", + pkgIdent.Name, pkgIdent.Name+"."+sx.Sel.Name, + )) + } + return impPath +} + +// simple equivalence between two BasicLits. +// Note that this returns true only if the expressions are exactly the same; +// ie. 16 != 0x10, only 16 == 16. +func basicLitsEqual(x1, x2 ast.Expr) bool { + if x1 == nil || x2 == nil { + return x1 == nil && x2 == nil + } + l1, ok1 := x1.(*ast.BasicLit) + l2, ok2 := x2.(*ast.BasicLit) + if !ok1 || !ok2 { + return false + } + return l1.Value == l2.Value +} + +// Signatures match when they accept the same elementary types, or a linked +// type mapping (see [linkedTypes]). +// +// Additionally, if the first parameter to the Go function is +// *[gnolang.Machine], it is ignored when matching to the Gno function. +func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { + if gnof.Type.TypeParams != nil || gof.Type.TypeParams != nil { + panic("type parameters not supported") + } + + // if first param of go function is *gno.Machine, remove it + gofp := gof.Type.Params + if gofp != nil && len(gofp.List) > 0 && m.isGnoMachine(gofp.List[0]) { + // avoid touching original struct + n := *gofp + n.List = n.List[1:] + gofp = &n + + m.MachineParam = true + } + + return m.fieldListsMatch(gnof.Type.Params, gofp) && + m.fieldListsMatch(gnof.Type.Results, gof.Type.Results) +} + +var errNoMatch = errors.New("no match") + +func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { + if gnofl == nil || gofl == nil { + return gnofl == nil && gofl == nil + } + if gnofl.NumFields() != gofl.NumFields() { + return false + } + err := iterFields(gnofl.List, gofl.List, func(gnoe, goe ast.Expr) error { + // if the go type is gno.TypedValue, we just don't perform reflect-based conversion. + if m.isTypedValue(goe) { + return nil + } + if m.mergeTypes(gnoe, goe) == nil { + return errNoMatch + } + return nil + }) + return err == nil +} + +// TODO: this is created based on the uverse definitions. This should be +// centralized, or at least have a CI/make check to make sure this stays the +// same +var builtinTypes = [...]string{ + "bool", + "string", + "int", + "int8", + "int16", + "rune", + "int32", + "int64", + "uint", + "byte", + "uint8", + "uint16", + "uint32", + "uint64", + "bigint", + "float32", + "float64", + "error", +} + +func isBuiltin(name string) bool { + for _, x := range builtinTypes { + if x == name { + return true + } + } + return false +} + +type linkedType struct { + gnoPackage string + gnoName string + goPackage string + goName string +} + +var linkedTypes = [...]linkedType{ + { + "std", "Address", + "github.com/gnolang/gno/tm2/pkg/crypto", "Bech32Address", + }, + { + "std", "Coin", + "github.com/gnolang/gno/tm2/pkg/std", "Coin", + }, + { + "std", "Coins", + "github.com/gnolang/gno/tm2/pkg/std", "Coins", + }, + { + "std", "Realm", + "github.com/gnolang/gno/gnovm/stdlibs/std", "Realm", + }, + { + "std", "BankerType", + "github.com/gnolang/gno/gnovm/stdlibs/std", "BankerType", + }, + { + "std", "Banker", + "github.com/gnolang/gno/gnovm/stdlibs/std", "Banker", + }, +} + +func linkedTypeExists(lt linkedType) bool { + for _, ltx := range linkedTypes { + if lt == ltx { + return true + } + } + return false +} diff --git a/misc/genstd/mapping_test.go b/misc/genstd/mapping_test.go new file mode 100644 index 00000000000..b0cfa1bd4a7 --- /dev/null +++ b/misc/genstd/mapping_test.go @@ -0,0 +1,294 @@ +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "os" + "path/filepath" + "sync" + "testing" + + "github.com/jaekwon/testify/assert" + "github.com/jaekwon/testify/require" +) + +const testdataDir = "github.com/gnolang/gno/misc/genstd/testdata/" + +var initWD = func() string { + d, err := os.Getwd() + if err != nil { + panic(err) + } + return d +}() + +func chdir(t *testing.T, s string) { + t.Helper() + + os.Chdir(filepath.Join(initWD, s)) + t.Cleanup(func() { + os.Chdir(initWD) + dirsOnce = sync.Once{} + memoGitRoot, memoRelPath = "", "" + }) +} + +func Test_linkFunctions(t *testing.T) { + chdir(t, "testdata/linkFunctions") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + mappings := linkFunctions(pkgs) + require.Len(t, mappings, 8) + + const ( + ret = 1 << iota + param + machine + ) + str := func(i int) string { + s := "Fn" + if i&machine != 0 { + s += "Machine" + } + if i¶m != 0 { + s += "Param" + } + if i&ret != 0 { + s += "Ret" + } + return s + } + + for i, v := range mappings { + exp := str(i) + assert.Equal(t, v.GnoFunc, exp) + assert.Equal(t, v.GoFunc, exp) + assert.Equal(t, v.GnoImportPath, "std") + assert.Equal(t, v.GoImportPath, testdataDir+"linkFunctions/std") + + assert.Equal(t, v.MachineParam, i&machine != 0, "MachineParam should match expected value") + if i¶m != 0 { + // require, otherwise the following would panic + require.Len(t, v.Params, 1) + p := v.Params[0] + assert.Equal(t, p.GnoType(), "int") + assert.Equal(t, p.GoQualifiedName(), "int") + assert.False(t, p.IsTypedValue) + } else { + assert.Len(t, v.Params, 0) + } + if i&ret != 0 { + // require, otherwise the following would panic + require.Len(t, v.Results, 1) + p := v.Results[0] + assert.Equal(t, p.GnoType(), "int") + assert.Equal(t, p.GoQualifiedName(), "int") + assert.False(t, p.IsTypedValue) + } else { + assert.Len(t, v.Results, 0) + } + } +} + +func Test_linkFunctions_unexp(t *testing.T) { + chdir(t, "testdata/linkFunctions_unexp") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + mappings := linkFunctions(pkgs) + require.Len(t, mappings, 2) + + assert.Equal(t, mappings[0].MachineParam, false) + assert.Equal(t, mappings[0].GnoFunc, "t1") + assert.Equal(t, mappings[0].GoFunc, "X_t1") + + assert.Equal(t, mappings[1].MachineParam, true) + assert.Equal(t, mappings[1].GnoFunc, "t2") + assert.Equal(t, mappings[1].GoFunc, "X_t2") +} + +func Test_linkFunctions_TypedValue(t *testing.T) { + chdir(t, "testdata/linkFunctions_TypedValue") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + mappings := linkFunctions(pkgs) + require.Len(t, mappings, 3) + + assert.Equal(t, mappings[0].MachineParam, false) + assert.Equal(t, mappings[0].GnoFunc, "TVParam") + assert.Equal(t, mappings[0].GoFunc, "TVParam") + assert.Len(t, mappings[0].Results, 0) + _ = assert.Len(t, mappings[0].Params, 1) && + assert.Equal(t, mappings[0].Params[0].IsTypedValue, true) && + assert.Equal(t, mappings[0].Params[0].GnoType(), "struct{m1 map[string]interface{}}") + + assert.Equal(t, mappings[1].MachineParam, false) + assert.Equal(t, mappings[1].GnoFunc, "TVResult") + assert.Equal(t, mappings[1].GoFunc, "TVResult") + assert.Len(t, mappings[1].Params, 0) + _ = assert.Len(t, mappings[1].Results, 1) && + assert.Equal(t, mappings[1].Results[0].IsTypedValue, true) && + assert.Equal(t, mappings[1].Results[0].GnoType(), "interface{S() map[int]Banker}") + + assert.Equal(t, mappings[2].MachineParam, true) + assert.Equal(t, mappings[2].GnoFunc, "TVFull") + assert.Equal(t, mappings[2].GoFunc, "TVFull") + assert.Len(t, mappings[2].Params, 1) + assert.Len(t, mappings[2].Results, 1) +} + +func Test_linkFunctions_noMatch(t *testing.T) { + chdir(t, "testdata/linkFunctions_noMatch") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + defer func() { + r := recover() + assert.NotNil(t, r) + assert.Contains(t, fmt.Sprint(r), "no matching go function declaration") + }() + + linkFunctions(pkgs) +} + +func Test_linkFunctions_noMatchSig(t *testing.T) { + chdir(t, "testdata/linkFunctions_noMatchSig") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + defer func() { + r := recover() + assert.NotNil(t, r) + assert.Contains(t, fmt.Sprint(r), "doesn't match signature of go function") + }() + + linkFunctions(pkgs) +} + +// mergeTypes - separate tests. + +var mergeTypesMapping = &mapping{ + GnoImportPath: "std", + GnoFunc: "Fn", + GoImportPath: "github.com/gnolang/gno/gnovm/stdlibs/std", + GoFunc: "Fn", + goImports: []*ast.ImportSpec{ + { + Name: &ast.Ident{Name: "gno"}, + Path: &ast.BasicLit{Value: `"github.com/gnolang/gno/gnovm/pkg/gnolang"`}, + }, + { + Path: &ast.BasicLit{Value: `"github.com/gnolang/gno/tm2/pkg/crypto"`}, + }, + }, + gnoImports: []*ast.ImportSpec{ + { + // cheating a bit -- but we currently only have linked types in `std`. + Path: &ast.BasicLit{Value: `"std"`}, + }, + { + Path: &ast.BasicLit{Value: `"math"`}, + }, + }, +} + +func Test_mergeTypes(t *testing.T) { + tt := []struct { + gnoe, goe string + result ast.Expr + }{ + {"int", "int", &ast.Ident{Name: "int"}}, + {"*[11][]rune", "*[11][]rune", &ast.StarExpr{ + X: &ast.ArrayType{Len: &ast.BasicLit{Value: "11"}, Elt: &ast.ArrayType{ + Elt: &ast.Ident{Name: "rune"}, + }}, + }}, + + {"Address", "crypto.Bech32Address", &linkedIdent{lt: linkedType{ + gnoPackage: "std", + gnoName: "Address", + goPackage: "github.com/gnolang/gno/tm2/pkg/crypto", + goName: "Bech32Address", + }}}, + {"std.Realm", "Realm", &linkedIdent{lt: linkedType{ + gnoPackage: "std", + gnoName: "Realm", + goPackage: "github.com/gnolang/gno/gnovm/stdlibs/std", + goName: "Realm", + }}}, + } + + for _, tv := range tt { + t.Run(tv.gnoe, func(t *testing.T) { + gnoe, err := parser.ParseExpr(tv.gnoe) + require.NoError(t, err) + goe, err := parser.ParseExpr(tv.goe) + require.NoError(t, err) + + result := mergeTypesMapping.mergeTypes(gnoe, goe) + assert.Equal(t, result, tv.result) + }) + } +} + +func Test_mergeTypes_invalid(t *testing.T) { + tt := []struct { + gnoe, goe string + panic string + }{ + {"int", "string", ""}, + + {"*int", "int", ""}, + {"string", "*string", ""}, + {"*string", "*int", ""}, + + {"[]int", "[1]int", ""}, + {"[1]int", "[]int", ""}, + {"[2]int", "[2]string", ""}, + // valid, but unsupported (only BasicLits) + {"[(11)]int", "[(11)]string", ""}, + + {"Address", "string", ""}, + {"math.X", "X", ""}, + + {"map[string]string", "map[string]string", "not implemented"}, + {"func(s string)", "func(s string)", "not implemented"}, + {"interface{}", "interface{}", "not implemented"}, + {"struct{}", "struct{}", "not implemented"}, + + {"1 + 2", "1 + 2", "invalid expression"}, + + // even though semantically equal, for simplicity we don't implement + // "true" basic lit equivalence + {"[8]int", "[0x8]int", ""}, + } + + for _, tv := range tt { + t.Run(tv.gnoe, func(t *testing.T) { + gnoe, err := parser.ParseExpr(tv.gnoe) + require.NoError(t, err) + goe, err := parser.ParseExpr(tv.goe) + require.NoError(t, err) + + defer func() { + r := recover() + if tv.panic == "" { + assert.Nil(t, r) + } else { + assert.Contains(t, fmt.Sprint(r), tv.panic) + } + }() + + result := mergeTypesMapping.mergeTypes(gnoe, goe) + assert.Nil(t, result) + }) + } +} diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl new file mode 100644 index 00000000000..f2cad0a851b --- /dev/null +++ b/misc/genstd/template.tmpl @@ -0,0 +1,88 @@ +// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. +// To regenerate it, run `go generate` from this directory. + +package stdlibs + +import ( + "reflect" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +{{- range .Imports }} + {{ .Name }} {{ printf "%q" .Path }} +{{- end }} +) + +type nativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + f func(m *gno.Machine) +} + +var nativeFuncs = [...]nativeFunc{ +{{- range $i, $m := .Mappings }} + { + {{ printf "%q" $m.GnoImportPath }}, + {{ printf "%q" $m.GnoFunc }}, + {{- /* TODO: set nil if empty */}} + []gno.FieldTypeExpr{ + {{- range $i, $p := $m.Params }} + {Name: gno.N("p{{ $i }}"), Type: gno.X({{ printf "%q" $p.GnoType }})}, + {{- end }} + }, + []gno.FieldTypeExpr{ + {{- range $i, $r := $m.Results }} + {Name: gno.N("r{{ $i }}"), Type: gno.X({{ printf "%q" $r.GnoType }})}, + {{- end }} + }, + func(m *gno.Machine) { + {{ if $m.Params -}} + b := m.LastBlock() + var ( + {{- range $pn, $pv := $m.Params -}} + {{- if $pv.IsTypedValue }} + p{{ $pn }} = gno.NewValuePathBlock(1, {{ $pn }}, "")).TV + {{- else }} + p{{ $pn }} {{ $pv.GoQualifiedName }} + rp{{ $pn }} = reflect.ValueOf(&p{{ $pn }}).Elem() + {{- end }} + {{- end }} + ) + + {{ range $pn, $pv := $m.Params -}} + {{- if not $pv.IsTypedValue }} + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, {{ $pn }}, "")).TV, rp{{ $pn }}) + {{- end -}} + {{ end }} + {{- end }} + + {{ range $rn, $rv := $m.Results -}} + {{- if gt $rn 0 -}}, {{ end -}} + r{{ $rn }} + {{- end -}} + {{- if $m.Results }} := {{ end -}} + {{ $.PkgName $m.GoImportPath }}.{{ $m.GoFunc }}( + {{- if $m.MachineParam }} + m, + {{ end -}} + {{- range $pn, $pv := $m.Params -}} + p{{ $pn }}, + {{- end -}} + ) + + {{ range $rn, $rv := $m.Results -}} + {{- if $rv.IsTypedValue }} + m.PushValue(r{{ $rn }}) + {{- else }} + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r{{ $rn }}).Elem(), {{- /* necessary to support interfaces (ie. error) */}} + )) + {{- end }} + {{- end }} + }, + }, +{{- end }} +} diff --git a/misc/genstd/testdata/linkFunctions/std/std.gno b/misc/genstd/testdata/linkFunctions/std/std.gno new file mode 100644 index 00000000000..ab04b4084ba --- /dev/null +++ b/misc/genstd/testdata/linkFunctions/std/std.gno @@ -0,0 +1,23 @@ +package std + +func Fn() + +func FnRet() int + +func FnParam(n int) + +func FnParamRet(n int) int + +func FnMachine() + +func FnMachineRet() int + +func FnMachineParam(n int) + +func FnMachineParamRet(n int) int + +func Ignored() int { + // Ignored even if it has a matching go definition - + // as this one has a body. + return 1 +} diff --git a/misc/genstd/testdata/linkFunctions/std/std.go b/misc/genstd/testdata/linkFunctions/std/std.go new file mode 100644 index 00000000000..1b7a791c6cc --- /dev/null +++ b/misc/genstd/testdata/linkFunctions/std/std.go @@ -0,0 +1,47 @@ +package std + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func Fn() { + println("call Fn") +} + +func FnRet() int { + println("call FnRet") + return 1 +} + +func FnParam(n int) { + println("call FnParam", n) +} + +func FnParamRet(n int) int { + println("call FnParamRet", n) + return 1 +} + +func FnMachine(m *gno.Machine) { + println("call FnMachine") +} + +func FnMachineRet(m *gno.Machine) int { + println("call FnMachineRet") + return 1 +} + +func FnMachineParam(m *gno.Machine, n int) { + println("call FnMachineParam", n) +} + +func FnMachineParamRet(m *gno.Machine, n int) int { + println("call FnMachineParamRet", n) + return 1 +} + +func Ignored() int { + // Ignored even if it has a matching go definition - + // as gno's has a body. + return 1 +} diff --git a/misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno b/misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno new file mode 100644 index 00000000000..3bba36774e3 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno @@ -0,0 +1,11 @@ +package std + +type Banker interface { + B() +} + +func TVParam(m struct{ m1 map[string]interface{} }) + +func TVResult() interface{ S() map[int]Banker } + +func TVFull(map[Banker]map[string]interface{}) (n [86]map[string]bool) diff --git a/misc/genstd/testdata/linkFunctions_TypedValue/std/std.go b/misc/genstd/testdata/linkFunctions_TypedValue/std/std.go new file mode 100644 index 00000000000..03d95721438 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_TypedValue/std/std.go @@ -0,0 +1,16 @@ +package std + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func TVParam(p gno.TypedValue) { +} + +func TVResult() gno.TypedValue { + return gno.TypedValue{} +} + +func TVFull(m *gno.Machine, v gno.TypedValue) gno.TypedValue { + return gno.TypedValue{} +} diff --git a/misc/genstd/testdata/linkFunctions_noMatch/std/std.gno b/misc/genstd/testdata/linkFunctions_noMatch/std/std.gno new file mode 100644 index 00000000000..2ef4be8abc6 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_noMatch/std/std.gno @@ -0,0 +1,3 @@ +package std + +func X() int diff --git a/misc/genstd/testdata/linkFunctions_noMatch/std/std.go b/misc/genstd/testdata/linkFunctions_noMatch/std/std.go new file mode 100644 index 00000000000..97399743533 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_noMatch/std/std.go @@ -0,0 +1,3 @@ +package std + +func Y() {} diff --git a/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.gno b/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.gno new file mode 100644 index 00000000000..75e8e10e2e3 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.gno @@ -0,0 +1,3 @@ +package std + +func X(n int) int diff --git a/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.go b/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.go new file mode 100644 index 00000000000..7a5a0e5893b --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.go @@ -0,0 +1,8 @@ +package std + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func X(m *gno.Machine, n string) { +} diff --git a/misc/genstd/testdata/linkFunctions_unexp/std/std.gno b/misc/genstd/testdata/linkFunctions_unexp/std/std.gno new file mode 100644 index 00000000000..c4811e5e837 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_unexp/std/std.gno @@ -0,0 +1,4 @@ +package std + +func t1() int +func t2() int diff --git a/misc/genstd/testdata/linkFunctions_unexp/std/std.go b/misc/genstd/testdata/linkFunctions_unexp/std/std.go new file mode 100644 index 00000000000..023b424e87c --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_unexp/std/std.go @@ -0,0 +1,13 @@ +package std + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func X_t1() int { + return 1 +} + +func X_t2(m *gno.Machine) int { + return m.NumOps +} diff --git a/misc/genstd/util.go b/misc/genstd/util.go new file mode 100644 index 00000000000..061a9604c67 --- /dev/null +++ b/misc/genstd/util.go @@ -0,0 +1,119 @@ +package main + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "sync" +) + +func runTool(importPath string) error { + shortName := path.Base(importPath) + gr := gitRoot() + + cmd := exec.Command( + "go", "run", "-modfile", filepath.Join(gr, "misc/devdeps/go.mod"), + importPath, "-w", "native.go", + ) + _, err := cmd.Output() + if err != nil { + if err, ok := err.(*exec.ExitError); ok { + return fmt.Errorf("error executing %s: %w; output: %v", shortName, err, string(err.Stderr)) + } + return fmt.Errorf("error executing %s: %w", shortName, err) + } + return nil +} + +var ( + memoGitRoot string + memoRelPath string + + dirsOnce sync.Once +) + +func gitRoot() string { + dirsOnceDo() + return memoGitRoot +} + +func relPath() string { + dirsOnceDo() + return memoRelPath +} + +func dirsOnceDo() { + dirsOnce.Do(func() { + var err error + memoGitRoot, memoRelPath, err = findDirs() + if err != nil { + panic(fmt.Errorf("could not determine git root: %w", err)) + } + }) +} + +func findDirs() (gitRoot string, relPath string, err error) { + wd, err := os.Getwd() + if err != nil { + return + } + p := wd + for { + if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { + // make relPath relative to the git root + rp := strings.TrimPrefix(wd, p+string(filepath.Separator)) + // normalize separator to / + rp = strings.ReplaceAll(rp, string(filepath.Separator), "/") + return p, rp, nil + } + + if strings.HasSuffix(p, string(filepath.Separator)) { + return "", "", errors.New("root git not found") + } + + p = filepath.Dir(p) + } +} + +// pkgNameFromPath derives the package name from the given path, +// unambiguously for the most part (so safe for the code generation). +// +// The path is taken and possibly shortened if it starts with a known prefix. +// For instance, github.com/gnolang/gno/stdlibs/std simply becomes "libs_std". +// "Unsafe" characters are removed (ie. invalid for go identifiers). +func pkgNameFromPath(path string) string { + const ( + repoPrefix = "github.com/gnolang/gno/" + vmPrefix = repoPrefix + "gnovm/" + tm2Prefix = repoPrefix + "tm2/pkg/" + libsPrefix = vmPrefix + "stdlibs/" + testlibsPrefix = vmPrefix + "tests/stdlibs/" + ) + + ns := "ext" + switch { + case strings.HasPrefix(path, testlibsPrefix): + ns, path = "testlibs", path[len(testlibsPrefix):] + case strings.HasPrefix(path, libsPrefix): + ns, path = "libs", path[len(libsPrefix):] + case strings.HasPrefix(path, vmPrefix): + ns, path = "vm", path[len(vmPrefix):] + case strings.HasPrefix(path, tm2Prefix): + ns, path = "tm2", path[len(tm2Prefix):] + case strings.HasPrefix(path, repoPrefix): + ns, path = "repo", path[len(repoPrefix):] + case !strings.Contains(path, "."): + ns = "go" + } + + flds := strings.FieldsFunc(path, func(r rune) bool { + return (r < 'a' || r > 'z') && + (r < 'A' || r > 'Z') && + (r < '0' || r > '9') + }) + return ns + "_" + strings.Join(flds, "_") +} diff --git a/misc/genstd/util_test.go b/misc/genstd/util_test.go new file mode 100644 index 00000000000..f6e804d545f --- /dev/null +++ b/misc/genstd/util_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/jaekwon/testify/assert" +) + +func Test_pkgNameFromPath(t *testing.T) { + tt := []struct { + input, result string + }{ + {"math", "go_math"}, + {"crypto/sha256", "go_crypto_sha256"}, + {"github.com/import/path", "ext_github_com_import_path"}, + // consecutive unsupported characters => _ + {"kebab----------case", "go_kebab_case"}, + + {"github.com/gnolang/gno/misc/test", "repo_misc_test"}, + {"github.com/gnolang/gno/tm2/pkg/crypto", "tm2_crypto"}, + {"github.com/gnolang/gno/gnovm/test", "vm_test"}, + {"github.com/gnolang/gno/gnovm/stdlibs/std", "libs_std"}, + {"github.com/gnolang/gno/gnovm/tests/stdlibs/std", "testlibs_std"}, + } + for i, tv := range tt { + t.Run(fmt.Sprintf("n%d", i+1), func(t *testing.T) { + assert.Equal(t, pkgNameFromPath(tv.input), tv.result) + }) + } +} diff --git a/misc/loop/Makefile b/misc/loop/Makefile index ab79b058ffb..d18c83fb84a 100644 --- a/misc/loop/Makefile +++ b/misc/loop/Makefile @@ -15,11 +15,11 @@ gnoland_dir := $(abspath ../../gno.land) all: loop -gnoland.start: +start.gnoland: cd $(gnoland_dir) && $(gnoland_bin) start -skip-failing-genesis-txs -genesis-txs-file $(HISTORY_OUTPUT) -gnoland.clean: +clean.gnoland: make -C $(gnoland_dir) fclean -.PHONY: gnoland.start gnoland.clean +.PHONY: start.gnoland clean.gnoland # Starts the backup service # and backs up transactions into a file @@ -36,7 +36,7 @@ save.history: cat $(BACKUP_FILE) >> $(HISTORY_OUTPUT) .PHONY: save.history -loop: gnoland.clean +loop: clean.gnoland # backup history, if needed $(MAKE) save.history || true # run our dev loop diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 10d2acd62bd..35baa515932 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -12,7 +12,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect - github.com/cockroachdb/apd v1.1.0 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgraph-io/badger/v3 v3.2103.4 // indirect diff --git a/misc/loop/go.sum b/misc/loop/go.sum index 846ee1340ed..d16d9f63ce9 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -33,8 +33,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf 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/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/misc/loop/run_loop.sh b/misc/loop/run_loop.sh index 99d1e5fbc04..4b186df9a71 100755 --- a/misc/loop/run_loop.sh +++ b/misc/loop/run_loop.sh @@ -22,7 +22,7 @@ echo "Running local development setup" # - start the backup service for transactions ( echo "Starting Gno node..." - make gnoland.start + make start.gnoland teardown ) & ( diff --git a/tm2/pkg/bft/config/toml.go b/tm2/pkg/bft/config/toml.go index 1599bc78968..c7e94c735f2 100644 --- a/tm2/pkg/bft/config/toml.go +++ b/tm2/pkg/bft/config/toml.go @@ -186,7 +186,7 @@ max_body_bytes = {{ .RPC.MaxBodyBytes }} max_header_bytes = {{ .RPC.MaxHeaderBytes }} # The path to a file containing certificate that is used to create the HTTPS server. -# Migth be either absolute path or path related to tendermint's config directory. +# Might be either absolute path or path related to tendermint's config directory. # If the certificate is signed by a certificate authority, # the certFile should be the concatenation of the server's certificate, any intermediates, # and the CA's certificate. @@ -194,7 +194,7 @@ max_header_bytes = {{ .RPC.MaxHeaderBytes }} tls_cert_file = "{{ .RPC.TLSCertFile }}" # The path to a file containing matching private key that is used to create the HTTPS server. -# Migth be either absolute path or path related to tendermint's config directory. +# Might be either absolute path or path related to tendermint's config directory. # NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. tls_key_file = "{{ .RPC.TLSKeyFile }}" diff --git a/tm2/pkg/bft/rpc/config/config.go b/tm2/pkg/bft/rpc/config/config.go index d0148badad1..2c24e713fc5 100644 --- a/tm2/pkg/bft/rpc/config/config.go +++ b/tm2/pkg/bft/rpc/config/config.go @@ -69,7 +69,7 @@ type RPCConfig struct { MaxHeaderBytes int `toml:"max_header_bytes"` // The path to a file containing certificate that is used to create the HTTPS server. - // Migth be either absolute path or path related to tendermint's config directory. + // Might be either absolute path or path related to tendermint's config directory. // // If the certificate is signed by a certificate authority, // the certFile should be the concatenation of the server's certificate, any intermediates, @@ -79,7 +79,7 @@ type RPCConfig struct { TLSCertFile string `toml:"tls_cert_file"` // The path to a file containing matching private key that is used to create the HTTPS server. - // Migth be either absolute path or path related to tendermint's config directory. + // Might be either absolute path or path related to tendermint's config directory. // // NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. TLSKeyFile string `toml:"tls_key_file"` diff --git a/tm2/pkg/std/account.go b/tm2/pkg/std/account.go index 604444b4046..c70f43d22e9 100644 --- a/tm2/pkg/std/account.go +++ b/tm2/pkg/std/account.go @@ -5,6 +5,11 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/errors" + + _ "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" + _ "github.com/gnolang/gno/tm2/pkg/crypto/mock" + _ "github.com/gnolang/gno/tm2/pkg/crypto/multisig" + _ "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" ) // Account is an interface used to store coins at a given address within state. diff --git a/tm2/pkg/std/memfile.go b/tm2/pkg/std/memfile.go index 99b8061ea3b..c632d3026d0 100644 --- a/tm2/pkg/std/memfile.go +++ b/tm2/pkg/std/memfile.go @@ -13,11 +13,15 @@ type MemFile struct { Body string } +// MemPackage represents the information and files of a package which will be +// stored in memory. It will generally be initialized by package gnolang's +// ReadMemPackage. +// // NOTE: in the future, a MemPackage may represent // updates/additional-files for an existing package. type MemPackage struct { - Name string - Path string + Name string // package name as declared by `package` + Path string // import path Files []*MemFile } diff --git a/tm2/pkg/std/package_test.go b/tm2/pkg/std/package_test.go new file mode 100644 index 00000000000..0a21188737b --- /dev/null +++ b/tm2/pkg/std/package_test.go @@ -0,0 +1,26 @@ +package std_test + +import ( + "testing" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/require" +) + +func TestAminoBaseAccount(t *testing.T) { + b := []byte(`{ + "address": "g1x90eh5ejc22548hjqznm2egyvn8ny36lqu460f", + "coins": "4200000ugnot", + "public_key": { + "@type": "/tm.PubKeySecp256k1", + "value": "AwMzujfppqEi8lozMVD8ORENUR8SIE06VLNP8FGL0aQ2" + }, + "account_number": "159", + "sequence": "33" +}`) + acc := std.BaseAccount{} + + err := amino.UnmarshalJSON(b, &acc) + require.NoError(t, err) +}