diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.md b/.github/ISSUE_TEMPLATE/BUG-REPORT.md index a63b450d678..3f2fe8452b5 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.md +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.md @@ -13,7 +13,7 @@ Describe your issue in as much detail as possible here ### Your environment -* Go version (example: go1.22.4) +* Go version (example: go1.23.6) * OS and CPU architecture (example: linux/amd64) * Gno commit hash causing the issue (example: f24690e7ebf325bffcfaf9e328c3df8e6b21e50c) @@ -37,4 +37,4 @@ Please paste any logs here that demonstrate the issue, if they exist ### Proposed solution -If you have an idea of how to fix this issue, please write it down here, so we can begin discussing it \ No newline at end of file +If you have an idea of how to fix this issue, please write it down here, so we can begin discussing it diff --git a/.github/codecov.yml b/.github/codecov.yml index f0cb9583cf2..d973bcce859 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -10,19 +10,10 @@ coverage: round: down precision: 2 status: - project: + patch: # new lines default: - target: auto - threshold: 5 # Let's decrease this later. - base: parent - if_no_uploads: error - if_not_found: success - if_ci_failed: error - only_pulls: false - patch: - default: - target: auto - threshold: 5 # Let's decrease this later. + target: 80 + threshold: 10 base: auto if_no_uploads: error if_not_found: success diff --git a/.github/golangci.yml b/.github/golangci.yml index afc581d2ec5..476aa8451fb 100644 --- a/.github/golangci.yml +++ b/.github/golangci.yml @@ -1,15 +1,13 @@ run: concurrency: 8 timeout: 10m - issue-exit-code: 1 + issues-exit-code: 1 tests: true - skip-dirs-use-default: true modules-download-mode: readonly allow-parallel-runners: false go: "" output: - uniq-by-line: false path-prefix: "" sort-results: true @@ -55,7 +53,7 @@ linters-settings: - G306 # Expect WriteFile permissions to be 0600 or less - G115 # Integer overflow conversion, no solution to check the overflow in time of convert, so linter shouldn't check the overflow. stylecheck: - checks: [ "all", "-ST1022", "-ST1003" ] + checks: ["all", "-ST1022", "-ST1003"] errorlint: asserts: false @@ -78,6 +76,8 @@ issues: max-same-issues: 0 new: false fix: false + exclude-dirs-use-default: true + uniq-by-line: false exclude-rules: - path: _test\.go diff --git a/.github/goreleaser.yaml b/.github/goreleaser.yaml index 71a8ba98745..a411484520d 100644 --- a/.github/goreleaser.yaml +++ b/.github/goreleaser.yaml @@ -23,10 +23,6 @@ builds: goarch: - amd64 - arm64 - - arm - goarm: - - "6" - - "7" - id: gnoland main: ./gno.land/cmd/gnoland binary: gnoland @@ -38,10 +34,6 @@ builds: goarch: - amd64 - arm64 - - arm - goarm: - - "6" - - "7" - id: gnokey main: ./gno.land/cmd/gnokey binary: gnokey @@ -53,10 +45,6 @@ builds: goarch: - amd64 - arm64 - - arm - goarm: - - "6" - - "7" - id: gnoweb main: ./gno.land/cmd/gnoweb binary: gnoweb @@ -68,10 +56,6 @@ builds: goarch: - amd64 - arm64 - - arm - goarm: - - "6" - - "7" - id: gnofaucet dir: ./contribs/gnofaucet binary: gnofaucet @@ -83,10 +67,20 @@ builds: goarch: - amd64 - arm64 - - arm - goarm: - - "6" - - "7" + - id: gnodev + dir: ./contribs/gnodev/cmd/gnodev + binary: gnodev + ldflags: + # using hardcoded ldflag + - -X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=/gnoroot + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 # Gno Contribs # NOTE: Contribs binary will be added in a single docker image below: gnocontribs - id: gnobro @@ -100,10 +94,6 @@ builds: goarch: - amd64 - arm64 - - arm - goarm: - - "6" - - "7" - id: gnogenesis dir: ./contribs/gnogenesis binary: gnogenesis @@ -115,10 +105,6 @@ builds: goarch: - amd64 - arm64 - - arm - goarm: - - "6" - - "7" gomod: proxy: true @@ -188,48 +174,6 @@ dockers: - examples - gnovm/stdlibs - gnovm/tests/stdlibs - - use: buildx - dockerfile: Dockerfile.release - goos: linux - goarch: arm - goarm: 6 - image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv6" - - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-armv6" - build_flag_templates: - - "--target=gno" - - "--platform=linux/arm/v6" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" - ids: - - gno - extra_files: - - examples - - gnovm/stdlibs - - gnovm/tests/stdlibs - - use: buildx - dockerfile: Dockerfile.release - goos: linux - goarch: arm - goarm: 7 - image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv7" - - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-armv7" - build_flag_templates: - - "--target=gno" - - "--platform=linux/arm/v7" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" - ids: - - gno - extra_files: - - examples - - gnovm/stdlibs - - gnovm/tests/stdlibs # gnoland - use: buildx @@ -274,51 +218,7 @@ dockers: - gno.land/genesis/genesis_txs.jsonl - examples - gnovm/stdlibs - - use: buildx - dockerfile: Dockerfile.release - goos: linux - goarch: arm - goarm: 6 - image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv6" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-armv6" - build_flag_templates: - - "--target=gnoland" - - "--platform=linux/arm/v6" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoland" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" - ids: - - gnoland - extra_files: - - gno.land/genesis/genesis_balances.txt - - gno.land/genesis/genesis_txs.jsonl - - examples - - gnovm/stdlibs - - use: buildx - dockerfile: Dockerfile.release - goos: linux - goarch: arm - goarm: 7 - image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv7" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-armv7" - build_flag_templates: - - "--target=gnoland" - - "--platform=linux/arm/v7" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoland" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" - ids: - - gnoland - extra_files: - - gno.land/genesis/genesis_balances.txt - - gno.land/genesis/genesis_txs.jsonl - - examples - - gnovm/stdlibs - + # gnokey - use: buildx dockerfile: Dockerfile.release @@ -352,40 +252,6 @@ dockers: - "--label=org.opencontainers.image.version={{.Version}}" ids: - gnokey - - use: buildx - dockerfile: Dockerfile.release - goos: linux - goarch: arm - goarm: 6 - image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv6" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-armv6" - build_flag_templates: - - "--target=gnokey" - - "--platform=linux/arm/v6" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}/gnokey" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" - ids: - - gnokey - - use: buildx - dockerfile: Dockerfile.release - goos: linux - goarch: arm - goarm: 7 - image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv7" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-armv7" - build_flag_templates: - - "--target=gnokey" - - "--platform=linux/arm/v7" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}/gnokey" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" - ids: - - gnokey # gnoweb - use: buildx @@ -420,40 +286,6 @@ dockers: - "--label=org.opencontainers.image.version={{.Version}}" ids: - gnoweb - - use: buildx - dockerfile: Dockerfile.release - goos: linux - goarch: arm - goarm: 6 - image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv6" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-armv6" - build_flag_templates: - - "--target=gnoweb" - - "--platform=linux/arm/v6" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoweb" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" - ids: - - gnoweb - - use: buildx - dockerfile: Dockerfile.release - goos: linux - goarch: arm - goarm: 7 - image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv7" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-armv7" - build_flag_templates: - - "--target=gnoweb" - - "--platform=linux/arm/v7" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}/gnoweb" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" - ids: - - gnoweb # gnofaucet - use: buildx @@ -488,59 +320,24 @@ dockers: - "--label=org.opencontainers.image.version={{.Version}}" ids: - gnofaucet - - use: buildx - dockerfile: Dockerfile.release - goos: linux - goarch: arm - goarm: 6 - image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-armv6" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-armv6" - build_flag_templates: - - "--target=gnofaucet" - - "--platform=linux/arm/v6" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}/gnofaucet" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" - ids: - - gnofaucet - - use: buildx - dockerfile: Dockerfile.release - goos: linux - goarch: arm - goarm: 7 - image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-armv7" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-armv7" - build_flag_templates: - - "--target=gnofaucet" - - "--platform=linux/arm/v7" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}/gnofaucet" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" - ids: - - gnofaucet - # gnocontribs + # gnodev - use: buildx dockerfile: Dockerfile.release goos: linux goarch: amd64 image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }}-amd64" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnodev:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnodev:{{ .Env.TAG_VERSION }}-amd64" build_flag_templates: - - "--target=gnocontribs" + - "--target=gnodev" - "--platform=linux/amd64" - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}/gnocontribs" + - "--label=org.opencontainers.image.title={{.ProjectName}}/gnodev" - "--label=org.opencontainers.image.revision={{.FullCommit}}" - "--label=org.opencontainers.image.version={{.Version}}" ids: - - gnobro - - gnogenesis + - gnodev extra_files: - gno.land/genesis/genesis_balances.txt - gno.land/genesis/genesis_txs.jsonl @@ -551,34 +348,34 @@ dockers: goos: linux goarch: arm64 image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }}-arm64v8" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnodev:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnodev:{{ .Env.TAG_VERSION }}-arm64v8" build_flag_templates: - - "--target=gnocontribs" + - "--target=gnodev" - "--platform=linux/arm64/v8" - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}/gnocontribs" + - "--label=org.opencontainers.image.title={{.ProjectName}}/gnodev" - "--label=org.opencontainers.image.revision={{.FullCommit}}" - "--label=org.opencontainers.image.version={{.Version}}" ids: - - gnobro - - gnogenesis + - gnodev extra_files: - gno.land/genesis/genesis_balances.txt - gno.land/genesis/genesis_txs.jsonl - examples - gnovm/stdlibs + + # gnocontribs - use: buildx dockerfile: Dockerfile.release goos: linux - goarch: arm - goarm: 6 + goarch: amd64 image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }}-armv6" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }}-armv6" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }}-amd64" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }}-amd64" build_flag_templates: - "--target=gnocontribs" - - "--platform=linux/arm/v6" + - "--platform=linux/amd64" - "--label=org.opencontainers.image.created={{.Date}}" - "--label=org.opencontainers.image.title={{.ProjectName}}/gnocontribs" - "--label=org.opencontainers.image.revision={{.FullCommit}}" @@ -594,14 +391,13 @@ dockers: - use: buildx dockerfile: Dockerfile.release goos: linux - goarch: arm - goarm: 7 + goarch: arm64 image_templates: - - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }}-armv7" - - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }}-armv7" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }}-arm64v8" + - "ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }}-arm64v8" build_flag_templates: - "--target=gnocontribs" - - "--platform=linux/arm/v7" + - "--platform=linux/arm64/v8" - "--label=org.opencontainers.image.created={{.Date}}" - "--label=org.opencontainers.image.title={{.ProjectName}}/gnocontribs" - "--label=org.opencontainers.image.revision={{.FullCommit}}" @@ -623,84 +419,70 @@ docker_manifests: image_templates: - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-armv7 - name_template: ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Env.TAG_VERSION }}-armv7 # gnoland - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Version }}-armv7 - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}/gnoland:{{ .Env.TAG_VERSION }}-armv7 # gnokey - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Version }}-armv7 - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}/gnokey:{{ .Env.TAG_VERSION }}-armv7 # gnoweb - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Version }}-armv7 - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}/gnoweb:{{ .Env.TAG_VERSION }}-armv7 # gnofaucet - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Version }}-armv7 - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}/gnofaucet:{{ .Env.TAG_VERSION }}-armv7 + + # gnodev + - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnodev:{{ .Version }} + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnodev:{{ .Version }}-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnodev:{{ .Version }}-arm64v8 + - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnodev:{{ .Env.TAG_VERSION }} + image_templates: + - ghcr.io/gnolang/{{ .ProjectName }}/gnodev:{{ .Env.TAG_VERSION }}-amd64 + - ghcr.io/gnolang/{{ .ProjectName }}/gnodev:{{ .Env.TAG_VERSION }}-arm64v8 # gnocontribs - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Version }}-armv7 - name_template: ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }} image_templates: - ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }}-amd64 - ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }}-arm64v8 - - ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }}-armv6 - - ghcr.io/gnolang/{{ .ProjectName }}/gnocontribs:{{ .Env.TAG_VERSION }}-armv7 docker_signs: - cmd: cosign diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml index add800fe2bf..19a9e3022eb 100644 --- a/.github/workflows/bot.yml +++ b/.github/workflows/bot.yml @@ -39,10 +39,12 @@ jobs: define-prs-matrix: name: Define PRs matrix # Skip this workflow if: + # - the bot is not configured on this repo/fork # - the bot is retriggering itself # - the event is emitted by codecov # - the event is a review on a pull request from a fork (see save-pr-number job below) if: | + vars.GH_BOT_LOGIN != '' && github.actor != vars.GH_BOT_LOGIN && github.actor != 'codecov[bot]' && (github.event_name != 'pull_request_review' || github.event.pull_request.base.repo.full_name == github.event.pull_request.head.repo.full_name) diff --git a/.github/workflows/build_template.yml b/.github/workflows/build_template.yml index a2c96f2d37e..00c14a66cb2 100644 --- a/.github/workflows/build_template.yml +++ b/.github/workflows/build_template.yml @@ -23,9 +23,14 @@ jobs: - name: Check generated files are up to date working-directory: ${{ inputs.modulepath }} 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 + if make -qp | grep -q '^generate:'; then + make generate + + if [ "$(git status -s)" != "" ]; then + echo "command 'make generate' creates files that differ from the git tree, please run 'make generate' and commit:" + git status -s + exit 1 + fi + else + echo "'make generate' rule not found, skipping." fi diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/docs-deploy.yml similarity index 100% rename from .github/workflows/deploy-docs.yml rename to .github/workflows/docs-deploy.yml diff --git a/.github/workflows/docs-generate.yml b/.github/workflows/docs-generate.yml new file mode 100644 index 00000000000..ac39b5b2f41 --- /dev/null +++ b/.github/workflows/docs-generate.yml @@ -0,0 +1,35 @@ +name: "Docs Generate & Lint" + +on: + pull_request: + paths: + - 'docs/**' + +jobs: + embed: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + + - name: make generate + working-directory: docs/ + run: make generate -B + + - name: Check if there is a diff + run: | + diff=$(git status --porcelain) + if [[ $diff ]]; then + echo 'Please run `make generate` in docs/.' + exit 1 + fi + + - name: make lint + working-directory: docs/ + run: make lint diff --git a/.github/workflows/docs-linter.yml b/.github/workflows/docs-linter.yml deleted file mode 100644 index d603d796ae9..00000000000 --- a/.github/workflows/docs-linter.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Docs Linter - -on: - push: - branches: - - master - pull_request: - paths: - - "docs/**" - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Install dependencies - run: go mod download - - - name: Build docs - run: make -C docs/ build - - - name: Check diff - run: git diff --exit-code || (echo "Some docs files are not formatted, please run 'make build'." && exit 1) - - - name: Run linter - run: make -C docs/ lint diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 5d606a2a663..966e11f6dee 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -6,9 +6,9 @@ on: - master pull_request: paths: - - gnovm/**/*.gno - - examples/**/*.gno - - examples/**/gno.mod + # any change to gnovm code can make examples fail + - gnovm/** + - examples/** concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -20,7 +20,7 @@ jobs: fail-fast: false matrix: goversion: - - "1.22.x" + - "1.23.x" runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -29,13 +29,13 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno transpile -v --gobuild ./examples + - run: go run ./gnovm/cmd/gno tool transpile -v --gobuild ./examples test: strategy: fail-fast: false matrix: goversion: - - "1.22.x" + - "1.23.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest timeout-minutes: 30 @@ -58,7 +58,7 @@ jobs: fail-fast: false matrix: goversion: - - "1.22.x" + - "1.23.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest timeout-minutes: 10 @@ -83,13 +83,13 @@ jobs: uses: ./.github/workflows/build_template.yml with: modulepath: "examples" - go-version: "1.22.x" + go-version: "1.23.x" mod-tidy: strategy: fail-fast: false matrix: - go-version: [ "1.22.x" ] + go-version: ["1.23.x"] # unittests: TODO: matrix with contracts runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/.github/workflows/genesis-verify.yml b/.github/workflows/genesis-verify.yml index acc41cc99ad..a9e9a43e55a 100644 --- a/.github/workflows/genesis-verify.yml +++ b/.github/workflows/genesis-verify.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - testnet: [ "test5.gno.land" ] + testnet: [ ] # Currently, all active testnet deployment genesis.json are legacy runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index a293469bb5d..2b27a2537e1 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,11 +1,14 @@ # generate Go docs and publish on gh-pages branch # Live at: https://gnolang.github.io/gno -name: Go Reference Docs Deployment +name: GitHub pages (godoc & stdlib_diff) build and deploy on: push: branches: - master + pull_request: + branches: + - master workflow_dispatch: permissions: @@ -19,29 +22,39 @@ concurrency: jobs: build: - if: ${{ github.repository == 'gnolang/gno' }} # Alternatively, validate based on provided tokens and permissions. + if: github.repository == 'gnolang/gno' # Alternatively, validate based on provided tokens and permissions. runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod - - run: echo "GOROOT=$(go env GOROOT)" >> $GITHUB_ENV - - run: echo $GOROOT + # Use the goroot at the top of the project to compare with the GnoVM + # stdlib, rather than the one in stdlib_diff (which may have a go.mod with + # a different toolchain version). + - run: echo "GOROOT_SAVE=$(go env GOROOT)" >> $GITHUB_ENV - run: "cd misc/stdlib_diff && make gen" - run: "cd misc/gendocs && make install gen" - run: "mkdir -p pages_output/stdlib_diff" - run: | cp -r misc/gendocs/godoc/* pages_output/ cp -r misc/stdlib_diff/stdlib_diff/* pages_output/stdlib_diff/ + + # These two last steps will be skipped on pull requests - uses: actions/configure-pages@v5 id: pages + if: github.event_name != 'pull_request' + - uses: actions/upload-pages-artifact@v3 + if: github.event_name != 'pull_request' with: path: ./pages_output deploy: - if: ${{ github.repository == 'gnolang/gno' }} # Alternatively, validate based on provided tokens and permissions. + if: > + github.repository == 'gnolang/gno' && + github.ref == 'refs/heads/master' && + github.event_name == 'push' runs-on: ubuntu-latest environment: name: github-pages diff --git a/.github/workflows/gnofmt_template.yml b/.github/workflows/gnofmt_template.yml index 096dbaa1b5d..edf9311f480 100644 --- a/.github/workflows/gnofmt_template.yml +++ b/.github/workflows/gnofmt_template.yml @@ -9,7 +9,7 @@ on: description: "Go version to use" required: false type: string - default: "1.22.x" + default: "1.23.x" jobs: fmt: @@ -30,4 +30,4 @@ jobs: - name: Check for unformatted code run: | - git diff --exit-code || (echo "Some gno files are not formatted, please run 'make fmt'." && exit 1) \ No newline at end of file + git diff --exit-code || (echo "Some gno files are not formatted, please run 'make fmt'." && exit 1) diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 0d3a7a10516..44644842618 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -11,6 +11,12 @@ on: # since there are integration suites that cover the gnovm / tm2 - gnovm/** - tm2/** + # Changes to examples/ can create failures in gno.land, eg. txtars, + # see: https://github.com/gnolang/gno/pull/3590 + - examples/** + # We trigger the testing workflow for changes to the main go.mod, + # since this can affect test results + - go.mod workflow_dispatch: jobs: @@ -27,7 +33,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: ["1.22.x"] + go-version: ["1.23.x"] # unittests: TODO: matrix with contracts runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/.github/workflows/gnovm.yml b/.github/workflows/gnovm.yml index 7a015b74e09..c5b503bb631 100644 --- a/.github/workflows/gnovm.yml +++ b/.github/workflows/gnovm.yml @@ -8,6 +8,10 @@ on: paths: - gnovm/** - tm2/** # GnoVM has a dependency on TM2 types + - examples/** # gnovm has some tests that depend on examples + # We trigger the testing workflow for changes to the main go.mod, + # since this can affect test results + - go.mod workflow_dispatch: jobs: diff --git a/.github/workflows/lint_template.yml b/.github/workflows/lint_template.yml index 43246572daa..0dbc4678edb 100644 --- a/.github/workflows/lint_template.yml +++ b/.github/workflows/lint_template.yml @@ -22,6 +22,5 @@ jobs: uses: golangci/golangci-lint-action@v6 with: working-directory: ${{ inputs.modulepath }} - args: - --config=${{ github.workspace }}/.github/golangci.yml - version: v1.62 # sync with misc/devdeps + args: --config=${{ github.workspace }}/.github/golangci.yml + version: v1.64 # sync with misc/devdeps diff --git a/.github/workflows/main_template.yml b/.github/workflows/main_template.yml index a463bb330ea..6b62a176cae 100644 --- a/.github/workflows/main_template.yml +++ b/.github/workflows/main_template.yml @@ -11,7 +11,7 @@ on: description: "Go version to use" required: false type: string - default: "1.22.x" + default: "1.23.x" secrets: codecov-token: required: true @@ -39,4 +39,3 @@ jobs: tests-extra-args: ${{ inputs.tests-extra-args }} secrets: codecov-token: ${{ secrets.codecov-token }} - diff --git a/.github/workflows/releaser-master.yml b/.github/workflows/releaser-master.yml index 7c81789b060..e8ae0a5777c 100644 --- a/.github/workflows/releaser-master.yml +++ b/.github/workflows/releaser-master.yml @@ -6,6 +6,9 @@ on: - master workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + permissions: contents: write # needed to write releases id-token: write # needed for keyless signing @@ -26,8 +29,8 @@ jobs: go-version-file: go.mod cache: true - - uses: sigstore/cosign-installer@v3.7.0 - - uses: anchore/sbom-action/download-syft@v0.17.9 + - uses: sigstore/cosign-installer@v3.8.1 + - uses: anchore/sbom-action/download-syft@v0.18.0 - uses: docker/login-action@v3 with: diff --git a/.github/workflows/releaser-nightly.yml b/.github/workflows/releaser-nightly.yml index 47b6cabb223..73f9515521f 100644 --- a/.github/workflows/releaser-nightly.yml +++ b/.github/workflows/releaser-nightly.yml @@ -23,8 +23,8 @@ jobs: go-version-file: go.mod cache: true - - uses: sigstore/cosign-installer@v3.7.0 - - uses: anchore/sbom-action/download-syft@v0.17.9 + - uses: sigstore/cosign-installer@v3.8.1 + - uses: anchore/sbom-action/download-syft@v0.18.0 - uses: docker/login-action@v3 with: diff --git a/.github/workflows/tm2.yml b/.github/workflows/tm2.yml index 757391eab8c..d2157eb8828 100644 --- a/.github/workflows/tm2.yml +++ b/.github/workflows/tm2.yml @@ -7,6 +7,9 @@ on: pull_request: paths: - tm2/** + # We trigger the testing workflow for changes to the main go.mod, + # since this can affect test results + - go.mod workflow_dispatch: jobs: diff --git a/.gitignore b/.gitignore index 7d3f3f92b41..82cc109887e 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ coverage.out *.swp *.swo *.bak + +.tmp/ +wal/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b58d63c6c75..2e0e989ccac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ The gno repository is primarily based on Go (Golang) and Gno. The primary tech stack for working on the repository: -- Go (version 1.22+) +- Go (version 1.23+) - make (for using Makefile configurations) It is recommended to work on a Unix environment, as most of the tooling is built around ready-made tools in Unix (WSL2 @@ -198,7 +198,7 @@ Additionally, it's not possible to use `gofumpt` for code formatting with (flycheck-define-checker gno-lint "A GNO syntax checker using the gno lint tool." - :command ("gno" "lint" source-original) + :command ("gno" "tool" "lint" source-original) :error-patterns (;; ./file.gno:32: error message (code=1) (error line-start (file-name) ":" line ": " (message) " (code=" (id (one-or-more digit)) ")." line-end)) ;; Ensure the file is saved, to work around @@ -514,4 +514,3 @@ automatic label management. | info needed | Issue is lacking information needed for resolving | | investigating | Issue is still being investigated by the team | | question | Issue starts a discussion or raises a question | - diff --git a/Dockerfile b/Dockerfile index effc30ca32f..057d4388415 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # build gno -FROM golang:1.22-alpine AS build-gno +FROM golang:1.23-alpine AS build-gno RUN go env -w GOMODCACHE=/root/.cache/go-build WORKDIR /gnoroot ENV GNOROOT="/gnoroot" @@ -11,7 +11,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build go build -o ./ RUN --mount=type=cache,target=/root/.cache/go-build go build -o ./build/gno ./gnovm/cmd/gno # build misc binaries -FROM golang:1.22-alpine AS build-misc +FROM golang:1.23-alpine AS build-misc RUN go env -w GOMODCACHE=/root/.cache/go-build WORKDIR /gnoroot ENV GNOROOT="/gnoroot" diff --git a/Dockerfile.release b/Dockerfile.release index c7bb1b582ed..ddca77a5ebf 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -44,6 +44,19 @@ COPY ./gnofaucet /usr/bin/gnofaucet EXPOSE 5050 ENTRYPOINT [ "/usr/bin/gnofaucet" ] +# +## ghcr.io/gnolang/gno/gnodev +FROM base as gnodev + +COPY ./gnodev /usr/bin/gnodev +COPY ./examples /gnoroot/examples/ +COPY ./gnovm/stdlibs /gnoroot/gnovm/stdlibs/ +COPY ./gno.land/genesis/genesis_balances.txt /gnoroot/gno.land/genesis/genesis_balances.txt +COPY ./gno.land/genesis/genesis_txs.jsonl /gnoroot/gno.land/genesis/genesis_txs.jsonl +# gnoweb exposed by default +EXPOSE 8888 +ENTRYPOINT [ "/usr/bin/gnodev" ] + # ## ghcr.io/gnolang/gno FROM base as gno diff --git a/README.md b/README.md index 89bfd96d74f..8ad62cca820 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ If you haven't already, take a moment to check out our [website](https://gno.lan > can use it to check out > [some](https://gno.land/r/demo/boards) > [example](https://gno.land/r/gnoland/blog) -> [contracts](https://gno.land/r/demo/users). +> [contracts](https://gno.land/r/gnoland/users/v1). > > Use the `[source]` button in the header to inspect the program's source; use > the `[help]` button to view how you can use [`gnokey`](./gno.land/cmd/gnokey) diff --git a/contribs/github-bot/Makefile b/contribs/github-bot/Makefile new file mode 100644 index 00000000000..aee43149b7a --- /dev/null +++ b/contribs/github-bot/Makefile @@ -0,0 +1,16 @@ +GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../../) + +rundep := go run -modfile ../../misc/devdeps/go.mod +golangci_lint := $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint + +.PHONY: install +install: + go install . + +.PHONY: lint +lint: + $(golangci_lint) --config ../../.github/golangci.yml run ./... + +.PHONY: test +test: + go test $(GOTEST_FLAGS) -v ./... diff --git a/contribs/github-bot/go.mod b/contribs/github-bot/go.mod index 8df55e3f282..e3072115c95 100644 --- a/contribs/github-bot/go.mod +++ b/contribs/github-bot/go.mod @@ -1,8 +1,6 @@ module github.com/gnolang/gno/contribs/github-bot -go 1.22 - -toolchain go1.22.2 +go 1.23.6 replace github.com/gnolang/gno => ../.. @@ -11,7 +9,7 @@ require ( github.com/google/go-github/v64 v64.0.0 github.com/migueleliasweb/go-github-mock v1.0.1 github.com/sethvargo/go-githubactions v1.3.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/xlab/treeprint v1.2.0 ) @@ -21,8 +19,8 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect golang.org/x/time v0.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/github-bot/go.sum b/contribs/github-bot/go.sum index 2dae4e83e72..e55a59cc6eb 100644 --- a/contribs/github-bot/go.sum +++ b/contribs/github-bot/go.sum @@ -20,14 +20,14 @@ github.com/sethvargo/go-githubactions v1.3.0 h1:Kg633LIUV2IrJsqy2MfveiED/Ouo+H2P github.com/sethvargo/go-githubactions v1.3.0/go.mod h1:7/4WeHgYfSz9U5vwuToCK9KPnELVHAhGtRwLREOQV80= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/contribs/github-bot/internal/check/comment.tmpl b/contribs/github-bot/internal/check/comment.tmpl index d9b633a69d5..acdb2681b92 100644 --- a/contribs/github-bot/internal/check/comment.tmpl +++ b/contribs/github-bot/internal/check/comment.tmpl @@ -27,7 +27,7 @@ 1. Complete manual checks for the PR, including the guidelines and additional checks if applicable. ##### 📚 Resources: -- [Report a bug with the bot](https://github.com/gnolang/gno/issues/3238). +- [Report a bug with the bot](https://www.github.com/gnolang/gno/issues/3238). - [View the bot’s configuration file](https://github.com/gnolang/gno/tree/master/contribs/github-bot/internal/config/config.go). {{ if or .AutoRules .ManualRules }}
Debug
diff --git a/contribs/github-bot/internal/conditions/author.go b/contribs/github-bot/internal/conditions/author.go index 9052f781bd5..19be8298849 100644 --- a/contribs/github-bot/internal/conditions/author.go +++ b/contribs/github-bot/internal/conditions/author.go @@ -58,3 +58,24 @@ func (a *authorInTeam) IsMet(pr *github.PullRequest, details treeprint.Tree) boo func AuthorInTeam(gh *client.GitHub, team string) Condition { return &authorInTeam{gh: gh, team: team} } + +type authorAssociationIs struct { + assoc string +} + +var _ Condition = &authorAssociationIs{} + +func (a *authorAssociationIs) IsMet(pr *github.PullRequest, details treeprint.Tree) bool { + detail := fmt.Sprintf("Pull request author has author_association: %q", a.assoc) + + return utils.AddStatusNode(pr.GetAuthorAssociation() == a.assoc, detail, details) +} + +// AuthorAssociationIs asserts that the author of the PR has the given value for +// the GitHub "author association" field, on the PR. +// +// See https://docs.github.com/en/graphql/reference/enums#commentauthorassociation +// for a list of possible values and descriptions. +func AuthorAssociationIs(association string) Condition { + return &authorAssociationIs{assoc: association} +} diff --git a/contribs/github-bot/internal/conditions/author_test.go b/contribs/github-bot/internal/conditions/author_test.go index c5836f1ea76..24d3c51859e 100644 --- a/contribs/github-bot/internal/conditions/author_test.go +++ b/contribs/github-bot/internal/conditions/author_test.go @@ -91,3 +91,30 @@ func TestAuthorInTeam(t *testing.T) { }) } } + +func TestAuthorAssociationIs(t *testing.T) { + t.Parallel() + + for _, testCase := range []struct { + name string + association string + associationWant string + isMet bool + }{ + {"has", "MEMBER", "MEMBER", true}, + {"hasNot", "COLLABORATOR", "MEMBER", false}, + } { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + pr := &github.PullRequest{ + AuthorAssociation: github.String(testCase.association), + } + details := treeprint.New() + condition := AuthorAssociationIs(testCase.associationWant) + + assert.Equal(t, condition.IsMet(pr, details), testCase.isMet, fmt.Sprintf("condition should have a met status: %t", testCase.isMet)) + assert.True(t, utils.TestLastNodeStatus(t, testCase.isMet, details), fmt.Sprintf("condition details should have a status: %t", testCase.isMet)) + }) + } +} diff --git a/contribs/github-bot/internal/conditions/draft.go b/contribs/github-bot/internal/conditions/draft.go index 2c263f2ae75..5d554b4b484 100644 --- a/contribs/github-bot/internal/conditions/draft.go +++ b/contribs/github-bot/internal/conditions/draft.go @@ -10,7 +10,7 @@ import ( // Draft Condition. type draft struct{} -var _ Condition = &baseBranch{} +var _ Condition = &draft{} func (*draft) IsMet(pr *github.PullRequest, details treeprint.Tree) bool { return utils.AddStatusNode(pr.GetDraft(), "This pull request is a draft", details) diff --git a/contribs/github-bot/internal/config/config.go b/contribs/github-bot/internal/config/config.go index f80fc86cb11..6fdc1a1cfc8 100644 --- a/contribs/github-bot/internal/config/config.go +++ b/contribs/github-bot/internal/config/config.go @@ -4,6 +4,7 @@ import ( "github.com/gnolang/gno/contribs/github-bot/internal/client" c "github.com/gnolang/gno/contribs/github-bot/internal/conditions" r "github.com/gnolang/gno/contribs/github-bot/internal/requirements" + "github.com/gnolang/gno/contribs/github-bot/internal/utils" ) type Teams []string @@ -32,28 +33,65 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { auto := []AutomaticCheck{ { Description: "Maintainers must be able to edit this pull request ([more info](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork))", - If: c.CreatedFromFork(), - Then: r.MaintainerCanModify(), + If: c.And( + c.BaseBranch("^master$"), + c.CreatedFromFork(), + ), + Then: r.MaintainerCanModify(), }, { Description: "Changes to 'docs' folder must be reviewed/authored by at least one devrel and one tech-staff", - If: c.FileChanged(gh, "^docs/"), + If: c.And( + c.BaseBranch("^master$"), + c.FileChanged(gh, "^docs/"), + ), Then: r.And( r.Or( r.AuthorInTeam(gh, "tech-staff"), - r.ReviewByTeamMembers(gh, "tech-staff", 1), + r.ReviewByTeamMembers(gh, "tech-staff").WithDesiredState(utils.ReviewStateApproved), ), r.Or( r.AuthorInTeam(gh, "devrels"), - r.ReviewByTeamMembers(gh, "devrels", 1), + r.ReviewByTeamMembers(gh, "devrels").WithDesiredState(utils.ReviewStateApproved), ), ), }, + { + Description: "Changes related to gnoweb must be reviewed by its codeowners", + If: c.And( + c.BaseBranch("^master$"), + c.FileChanged(gh, "^gno.land/pkg/gnoweb/"), + ), + Then: r.Or( + r.ReviewByUser(gh, "alexiscolin"), + r.ReviewByUser(gh, "gfanton"), + ), + }, { Description: "Must not contain the \"don't merge\" label", If: c.Label("don't merge"), Then: r.Never(), }, + { + Description: "Pending initial approval by a review team member, or review from tech-staff", + If: c.And( + c.BaseBranch("^master$"), + c.Not(c.AuthorInTeam(gh, "tech-staff")), + ), + Then: r. + If(r.Or( + r.ReviewByOrgMembers(gh).WithDesiredState(utils.ReviewStateApproved), + r.ReviewByTeamMembers(gh, "tech-staff"), + r.Draft(), + )). + // Either there was a first approval from a member, and we + // assert that the label for triage-pending is removed... + Then(r.Not(r.Label(gh, "review/triage-pending", r.LabelRemove))). + // Or there was not, and we apply the triage pending label. + // The requirement should always fail, to mark the PR is not + // ready to be merged. + Else(r.And(r.Label(gh, "review/triage-pending", r.LabelApply), r.Never())), + }, } manual := []ManualCheck{ @@ -73,7 +111,7 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { { Description: "Determine if infra needs to be updated before merging", If: c.And( - c.BaseBranch("master"), + c.BaseBranch("^master$"), c.Or( c.FileChanged(gh, `Dockerfile`), c.FileChanged(gh, `^misc/deployments`), diff --git a/contribs/github-bot/internal/requirements/boolean.go b/contribs/github-bot/internal/requirements/boolean.go index 6b441c92f80..7d9eea2f0d9 100644 --- a/contribs/github-bot/internal/requirements/boolean.go +++ b/contribs/github-bot/internal/requirements/boolean.go @@ -96,3 +96,69 @@ func (n *not) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { func Not(req Requirement) Requirement { return ¬{req} } + +// IfCondition executes the condition, and based on the result then runs Then +// or Else. +type IfCondition struct { + cond Requirement + then Requirement + els Requirement +} + +var _ Requirement = &IfCondition{} + +func (i *IfCondition) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + if i.then == nil { + i.then = Always() + } + ifBranch := details.AddBranch("") + condBranch := ifBranch.AddBranch("") + + var ( + target Requirement + targetName string + ) + + if i.cond.IsSatisfied(pr, condBranch) { + condBranch.SetValue(fmt.Sprintf("%s Condition", utils.Success)) + target, targetName = i.then, "Then" + } else { + condBranch.SetValue(fmt.Sprintf("%s Condition", utils.Fail)) + target, targetName = i.els, "Else" + } + + targBranch := ifBranch.AddBranch("") + if target == nil || target.IsSatisfied(pr, targBranch) { + ifBranch.SetValue(fmt.Sprintf("%s If", utils.Success)) + targBranch.SetValue(fmt.Sprintf("%s %s", utils.Success, targetName)) + return true + } else { + ifBranch.SetValue(fmt.Sprintf("%s If", utils.Fail)) + targBranch.SetValue(fmt.Sprintf("%s %s", utils.Fail, targetName)) + return false + } +} + +// If returns a conditional requirement, which runs Then if cond evaluates +// successfully, or Else otherwise. +// +// Then / Else are optional, and always evaluate to true by default. +func If(cond Requirement) *IfCondition { + return &IfCondition{cond: cond} +} + +func (i *IfCondition) Then(then Requirement) *IfCondition { + if i.then != nil { + panic("'Then' is already set") + } + i.then = then + return i +} + +func (i *IfCondition) Else(els Requirement) *IfCondition { + if i.els != nil { + panic("'Else' is already set") + } + i.els = els + return i +} diff --git a/contribs/github-bot/internal/requirements/boolean_test.go b/contribs/github-bot/internal/requirements/boolean_test.go index 0043a44985c..dfa829ede61 100644 --- a/contribs/github-bot/internal/requirements/boolean_test.go +++ b/contribs/github-bot/internal/requirements/boolean_test.go @@ -94,3 +94,80 @@ func TestNot(t *testing.T) { }) } } + +func TestIfCond(t *testing.T) { + t.Parallel() + + for _, testCase := range []struct { + name string + req Requirement + isSatisfied bool + }{ + {"if always", If(Always()), true}, + {"if never", If(Never()), true}, + {"if always then always", If(Always()).Then(Always()), true}, + {"if never else always", If(Never()).Else(Always()), true}, + {"if always then never", If(Always()).Then(Never()), false}, + {"if never else never", If(Never()).Else(Never()), false}, + } { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + pr := &github.PullRequest{} + details := treeprint.New() + + actual := testCase.req.IsSatisfied(pr, details) + assert.Equal(t, testCase.isSatisfied, actual, + "requirement should have a satisfied status: %t", testCase.isSatisfied) + assert.True(t, + utils.TestNodeStatus(t, testCase.isSatisfied, details.(*treeprint.Node).Nodes[0]), + "requirement details should have a status: %t", testCase.isSatisfied) + }) + } +} + +type reqFunc func(*github.PullRequest, treeprint.Tree) bool + +func (r reqFunc) IsSatisfied(gh *github.PullRequest, details treeprint.Tree) bool { + return r(gh, details) +} + +func TestIfCond_ConditionalExecution(t *testing.T) { + t.Run("executeThen", func(t *testing.T) { + thenExec, elseExec := 0, 0 + If(Always()). + Then(reqFunc(func(*github.PullRequest, treeprint.Tree) bool { + thenExec++ + return true + })). + Else(reqFunc(func(*github.PullRequest, treeprint.Tree) bool { + elseExec++ + return true + })).IsSatisfied(nil, treeprint.New()) + assert.Equal(t, 1, thenExec, "Then should be executed 1 time") + assert.Equal(t, 0, elseExec, "Else should be executed 0 time") + }) + t.Run("executeElse", func(t *testing.T) { + thenExec, elseExec := 0, 0 + If(Never()). + Then(reqFunc(func(*github.PullRequest, treeprint.Tree) bool { + thenExec++ + return true + })). + Else(reqFunc(func(*github.PullRequest, treeprint.Tree) bool { + elseExec++ + return true + })).IsSatisfied(nil, treeprint.New()) + assert.Equal(t, 0, thenExec, "Then should be executed 0 time") + assert.Equal(t, 1, elseExec, "Else should be executed 1 time") + }) +} + +func TestIfCond_NoRepeats(t *testing.T) { + assert.Panics(t, func() { + If(Always()).Then(Always()).Then(Always()) + }, "two Then should panic") + assert.Panics(t, func() { + If(Always()).Else(Always()).Else(Always()) + }, "two Else should panic") +} diff --git a/contribs/github-bot/internal/requirements/draft.go b/contribs/github-bot/internal/requirements/draft.go new file mode 100644 index 00000000000..675ffa02090 --- /dev/null +++ b/contribs/github-bot/internal/requirements/draft.go @@ -0,0 +1,21 @@ +package requirements + +import ( + "github.com/gnolang/gno/contribs/github-bot/internal/utils" + + "github.com/google/go-github/v64/github" + "github.com/xlab/treeprint" +) + +// Draft Condition. +type draft struct{} + +var _ Requirement = &draft{} + +func (*draft) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + return utils.AddStatusNode(pr.GetDraft(), "This pull request is a draft", details) +} + +func Draft() Requirement { + return &draft{} +} diff --git a/contribs/github-bot/internal/requirements/draft_test.go b/contribs/github-bot/internal/requirements/draft_test.go new file mode 100644 index 00000000000..ded4917b808 --- /dev/null +++ b/contribs/github-bot/internal/requirements/draft_test.go @@ -0,0 +1,34 @@ +package requirements + +import ( + "fmt" + "testing" + + "github.com/gnolang/gno/contribs/github-bot/internal/utils" + "github.com/google/go-github/v64/github" + "github.com/stretchr/testify/assert" + "github.com/xlab/treeprint" +) + +func TestDraft(t *testing.T) { + t.Parallel() + + for _, testCase := range []struct { + name string + isMet bool + }{ + {"draft is true", true}, + {"draft is false", false}, + } { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + pr := &github.PullRequest{Draft: &testCase.isMet} + details := treeprint.New() + req := Draft() + + assert.Equal(t, req.IsSatisfied(pr, details), testCase.isMet, fmt.Sprintf("condition should have a met status: %t", testCase.isMet)) + assert.True(t, utils.TestLastNodeStatus(t, testCase.isMet, details), fmt.Sprintf("condition details should have a status: %t", testCase.isMet)) + }) + } +} diff --git a/contribs/github-bot/internal/requirements/label.go b/contribs/github-bot/internal/requirements/label.go index d1ee475db92..783c7a94ae8 100644 --- a/contribs/github-bot/internal/requirements/label.go +++ b/contribs/github-bot/internal/requirements/label.go @@ -10,10 +10,23 @@ import ( "github.com/xlab/treeprint" ) +// LabelAction controls what to do with the given label. +type LabelAction byte + +const ( + // LabelApply will place the label on the PR if it doesn't exist. + LabelApply = iota + // LabelRemove will remove the label from the PR if it exists. + LabelRemove + // LabelIgnore always leaves the label on the PR as-is, without modifying it. + LabelIgnore +) + // Label Requirement. type label struct { - gh *client.GitHub - name string + gh *client.GitHub + name string + action LabelAction } var _ Requirement = &label{} @@ -21,33 +34,58 @@ var _ Requirement = &label{} func (l *label) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { detail := fmt.Sprintf("This label is applied to pull request: %s", l.name) + found := false // Check if label was already applied to PR. for _, label := range pr.Labels { if l.name == label.GetName() { - return utils.AddStatusNode(true, detail, details) + found = true + break } } - // If in a dry run, skip applying the label. - if l.gh.DryRun { - return utils.AddStatusNode(false, detail, details) + // If in a dry run, or no action expected, skip applying the label. + if l.gh.DryRun || + l.action == LabelIgnore || + (l.action == LabelApply && found) || + (l.action == LabelRemove && !found) { + return utils.AddStatusNode(found, detail, details) } - // If label not already applied, apply it. - if _, _, err := l.gh.Client.Issues.AddLabelsToIssue( - l.gh.Ctx, - l.gh.Owner, - l.gh.Repo, - pr.GetNumber(), - []string{l.name}, - ); err != nil { - l.gh.Logger.Errorf("Unable to add label %s to PR %d: %v", l.name, pr.GetNumber(), err) + switch l.action { + case LabelApply: + // If label not already applied, apply it. + if _, _, err := l.gh.Client.Issues.AddLabelsToIssue( + l.gh.Ctx, + l.gh.Owner, + l.gh.Repo, + pr.GetNumber(), + []string{l.name}, + ); err != nil { + l.gh.Logger.Errorf("Unable to add label %s to PR %d: %v", l.name, pr.GetNumber(), err) + return utils.AddStatusNode(false, detail, details) + } + return utils.AddStatusNode(true, detail, details) + case LabelRemove: + // If label not already applied, apply it. + if _, err := l.gh.Client.Issues.RemoveLabelForIssue( + l.gh.Ctx, + l.gh.Owner, + l.gh.Repo, + pr.GetNumber(), + l.name, + ); err != nil { + l.gh.Logger.Errorf("Unable to remove label %s from PR %d: %v", l.name, pr.GetNumber(), err) + return utils.AddStatusNode(true, detail, details) + } return utils.AddStatusNode(false, detail, details) + default: + panic(fmt.Sprintf("invalid LabelAction value: %d", l.action)) } - - return utils.AddStatusNode(true, detail, details) } -func Label(gh *client.GitHub, name string) Requirement { - return &label{gh, name} +// Label asserts that the label with the given name is not applied to the PR. +// +// If it's not a dry run, the label will be applied to the PR. +func Label(gh *client.GitHub, name string, action LabelAction) Requirement { + return &label{gh, name, action} } diff --git a/contribs/github-bot/internal/requirements/label_test.go b/contribs/github-bot/internal/requirements/label_test.go index 631bff9e64b..e59fa821dc3 100644 --- a/contribs/github-bot/internal/requirements/label_test.go +++ b/contribs/github-bot/internal/requirements/label_test.go @@ -25,11 +25,11 @@ func TestLabel(t *testing.T) { } for _, testCase := range []struct { - name string - pattern string - labels []*github.Label - dryRun bool - exists bool + name string + labelName string + prLabels []*github.Label + dryRun bool + exists bool }{ {"empty label list", "label", []*github.Label{}, false, false}, {"empty label list with dry-run", "user", []*github.Label{}, true, false}, @@ -60,9 +60,9 @@ func TestLabel(t *testing.T) { DryRun: testCase.dryRun, } - pr := &github.PullRequest{Labels: testCase.labels} + pr := &github.PullRequest{Labels: testCase.prLabels} details := treeprint.New() - requirement := Label(gh, testCase.pattern) + requirement := Label(gh, testCase.labelName, LabelApply) assert.True(t, requirement.IsSatisfied(pr, details) || testCase.dryRun, "requirement should have a satisfied status: true") assert.True(t, utils.TestLastNodeStatus(t, true, details) || testCase.dryRun, "requirement details should have a status: true") @@ -70,3 +70,60 @@ func TestLabel(t *testing.T) { }) } } + +func TestLabel_LabelRemove(t *testing.T) { + t.Parallel() + + labels := []*github.Label{ + {Name: github.String("notTheRightOne")}, + {Name: github.String("label")}, + {Name: github.String("anotherOne")}, + } + + for _, testCase := range []struct { + name string + labelName string + prLabels []*github.Label + dryRun bool + shouldRequest bool + result bool + }{ + {"empty label list", "label", []*github.Label{}, false, false, false}, + {"empty label list with dry-run", "label", []*github.Label{}, true, false, false}, + {"label list contains label", "label", labels, false, true, false}, + {"label list contains label with dry-run", "label", labels, true, false, true}, + {"label list doesn't contain label", "label2", labels, false, false, false}, + } { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + requested := false + mockedHTTPClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{ + Pattern: "/repos/issues/0/labels/label", + Method: "GET", + }, + http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + requested = true + }), + ), + ) + + gh := &client.GitHub{ + Client: github.NewClient(mockedHTTPClient), + Ctx: context.Background(), + Logger: logger.NewNoopLogger(), + DryRun: testCase.dryRun, + } + + pr := &github.PullRequest{Labels: testCase.prLabels} + details := treeprint.New() + requirement := Label(gh, testCase.labelName, LabelRemove) + + assert.Equal(t, testCase.result, requirement.IsSatisfied(pr, details), "result of IsSatisfied should match expectation") + assert.True(t, utils.TestLastNodeStatus(t, testCase.result, details), "requirement details should have a status: %t", testCase.result) + assert.Equal(t, testCase.shouldRequest, requested, "IsSatisfied should have requested to delete item") + }) + } +} diff --git a/contribs/github-bot/internal/requirements/reviewer.go b/contribs/github-bot/internal/requirements/reviewer.go index aa3914d4c4a..6562c30df69 100644 --- a/contribs/github-bot/internal/requirements/reviewer.go +++ b/contribs/github-bot/internal/requirements/reviewer.go @@ -2,6 +2,7 @@ package requirements import ( "fmt" + "slices" "github.com/gnolang/gno/contribs/github-bot/internal/client" "github.com/gnolang/gno/contribs/github-bot/internal/utils" @@ -10,16 +11,82 @@ import ( "github.com/xlab/treeprint" ) -// Reviewer Requirement. -type reviewByUser struct { - gh *client.GitHub - user string +// deduplicateReviews returns a list of reviews with at most 1 review per +// author, where approval/changes requested reviews are preferred over comments +// and later reviews are preferred over earlier ones. +func deduplicateReviews(reviews []*github.PullRequestReview) []*github.PullRequestReview { + added := make(map[string]int) + result := make([]*github.PullRequestReview, 0, len(reviews)) + for _, rev := range reviews { + idx, ok := added[rev.User.GetLogin()] + switch utils.ReviewState(rev.GetState()) { + case utils.ReviewStateApproved, utils.ReviewStateChangesRequested: + // this review changes the "approval state", and is more relevant, + // so substitute it with the previous one if it exists. + if ok { + result[idx] = rev + } else { + result = append(result, rev) + added[rev.User.GetLogin()] = len(result) - 1 + } + case utils.ReviewStateCommented: + // this review does not change the "approval state", so only append + // it if a previous review doesn't exist. + if !ok { + result = append(result, rev) + added[rev.User.GetLogin()] = len(result) - 1 + } + case utils.ReviewStateDismissed: + // this state just dismisses any previous review, so remove previous + // entry for this user if it exists. + if ok { + result[idx] = nil + } + default: + panic(fmt.Sprintf("invalid review state %q", rev.GetState())) + } + } + // Remove nil entries from the result (dismissed reviews). + result = slices.DeleteFunc(result, func(r *github.PullRequestReview) bool { + return r == nil + }) + + return result } -var _ Requirement = &reviewByUser{} +// ReviewByUserRequirement asserts that there is a review by the given user, +// and if given that the review matches the desiredState. +type ReviewByUserRequirement struct { + gh *client.GitHub + user string + desiredState string +} + +var _ Requirement = &ReviewByUserRequirement{} + +// IsSatisfied implements [Requirement]. +func (r *ReviewByUserRequirement) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + detail := fmt.Sprintf("This user reviewed pull request: %s", r.user) + if r.desiredState != "" { + detail += fmt.Sprintf(" (with state %q)", r.desiredState) + } + + // Check if user already approved this PR. + reviews, err := r.gh.ListPRReviews(pr.GetNumber()) + if err != nil { + r.gh.Logger.Errorf("unable to check if user %s already approved this PR: %v", r.user, err) + return utils.AddStatusNode(false, detail, details) + } + reviews = deduplicateReviews(reviews) -func (r *reviewByUser) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { - detail := fmt.Sprintf("This user approved pull request: %s", r.user) + for _, review := range reviews { + if review.GetUser().GetLogin() == r.user { + r.gh.Logger.Debugf("User %s already reviewed PR %d with state %s", r.user, pr.GetNumber(), review.GetState()) + result := r.desiredState == "" || review.GetState() == r.desiredState + return utils.AddStatusNode(result, detail, details) + } + } + r.gh.Logger.Debugf("User %s has not reviewed PR %d yet", r.user, pr.GetNumber()) // If not a dry run, make the user a reviewer if he's not already. if !r.gh.DryRun { @@ -55,43 +122,65 @@ func (r *reviewByUser) IsSatisfied(pr *github.PullRequest, details treeprint.Tre } } - // Check if user already approved this PR. - reviews, err := r.gh.ListPRReviews(pr.GetNumber()) - if err != nil { - r.gh.Logger.Errorf("unable to check if user %s already approved this PR: %v", r.user, err) - return utils.AddStatusNode(false, detail, details) - } + return utils.AddStatusNode(false, detail, details) +} - for _, review := range reviews { - if review.GetUser().GetLogin() == r.user { - r.gh.Logger.Debugf("User %s already reviewed PR %d with state %s", r.user, pr.GetNumber(), review.GetState()) - return utils.AddStatusNode(review.GetState() == "APPROVED", detail, details) - } +// WithDesiredState asserts that the review by the given user should also be +// of the given ReviewState. +// +// If an empty string is passed, then all reviews are counted. This is the default. +func (r *ReviewByUserRequirement) WithDesiredState(s utils.ReviewState) *ReviewByUserRequirement { + if s != "" && !s.Valid() { + panic(fmt.Sprintf("invalid state: %q", s)) } - r.gh.Logger.Debugf("User %s has not reviewed PR %d yet", r.user, pr.GetNumber()) - - return utils.AddStatusNode(false, detail, details) + r.desiredState = string(s) + return r } -func ReviewByUser(gh *client.GitHub, user string) Requirement { - return &reviewByUser{gh, user} +// ReviewByUser asserts that the PR has been reviewed by the given user. +func ReviewByUser(gh *client.GitHub, user string) *ReviewByUserRequirement { + return &ReviewByUserRequirement{gh: gh, user: user} } -// Reviewer Requirement. -type reviewByTeamMembers struct { - gh *client.GitHub - team string - count uint +// ReviewByTeamMembersRequirement asserts that count members of the given team +// have reviewed the PR. Additionally, using desiredState, it may be required +// that the PR reviews be of that state. +type ReviewByTeamMembersRequirement struct { + gh *client.GitHub + team string + count uint + desiredState string } -var _ Requirement = &reviewByTeamMembers{} +var _ Requirement = &ReviewByTeamMembersRequirement{} -func (r *reviewByTeamMembers) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { - detail := fmt.Sprintf("At least %d user(s) of the team %s approved pull request", r.count, r.team) +// IsSatisfied implements [Requirement]. +func (r *ReviewByTeamMembersRequirement) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + detail := fmt.Sprintf("At least %d user(s) of the team %s reviewed pull request", r.count, r.team) + if r.desiredState != "" { + detail += fmt.Sprintf("(with state %q)", r.desiredState) + } - // If not a dry run, make the user a reviewer if he's not already. + teamMembers, err := r.gh.ListTeamMembers(r.team) + if err != nil { + r.gh.Logger.Errorf(err.Error()) + return utils.AddStatusNode(false, detail, details) + } + + reviews, err := r.gh.ListPRReviews(pr.GetNumber()) + if err != nil { + r.gh.Logger.Errorf("unable to fetch existing reviews of pr %d: %v", pr.GetNumber(), err) + return utils.AddStatusNode(false, detail, details) + } + + reviews = deduplicateReviews(reviews) + + // If not a dry run, request a team review if no member has reviewed yet, + // and the team review has not been requested. if !r.gh.DryRun { - requested := false + var teamRequested bool + var usersRequested []string + reviewers, err := r.gh.ListPRReviewers(pr.GetNumber()) if err != nil { r.gh.Logger.Errorf("unable to check if team %s review is already requested: %v", r.team, err) @@ -100,14 +189,34 @@ func (r *reviewByTeamMembers) IsSatisfied(pr *github.PullRequest, details treepr for _, team := range reviewers.Teams { if team.GetSlug() == r.team { - requested = true + teamRequested = true break } } - if requested { + if !teamRequested { + for _, user := range reviewers.Users { + if containsUserWithLogin(teamMembers, user.GetLogin()) { + usersRequested = append(usersRequested, user.GetLogin()) + } + } + + for _, rev := range reviews { + // if not already requested and user is a team member... + if !slices.Contains(usersRequested, rev.User.GetLogin()) && + containsUserWithLogin(teamMembers, rev.User.GetLogin()) { + usersRequested = append(usersRequested, rev.User.GetLogin()) + } + } + } + + switch { + case teamRequested: r.gh.Logger.Debugf("Review of team %s already requested on PR %d", r.team, pr.GetNumber()) - } else { + case len(usersRequested) > 0: + r.gh.Logger.Debugf("Members %v of team %s already requested on (or reviewed) PR %d", + usersRequested, r.team, pr.GetNumber()) + default: r.gh.Logger.Debugf("Requesting review from team %s on PR %d", r.team, pr.GetNumber()) if _, _, err := r.gh.Client.PullRequests.RequestReviewers( r.gh.Ctx, @@ -123,34 +232,131 @@ func (r *reviewByTeamMembers) IsSatisfied(pr *github.PullRequest, details treepr } } - // Check how many members of this team already approved this PR. - approved := uint(0) + // Check how many members of this team already reviewed this PR. + reviewCount := uint(0) + + for _, review := range reviews { + login := review.GetUser().GetLogin() + if containsUserWithLogin(teamMembers, login) { + if desired := r.desiredState; desired == "" || desired == review.GetState() { + reviewCount += 1 + } + r.gh.Logger.Debugf( + "Member %s from team %s already reviewed PR %d with state %s (%d/%d required review(s) with state %q)", + login, r.team, pr.GetNumber(), review.GetState(), reviewCount, r.count, r.desiredState, + ) + } + } + + return utils.AddStatusNode(reviewCount >= r.count, detail, details) +} + +func containsUserWithLogin(users []*github.User, login string) bool { + return slices.ContainsFunc(users, func(u *github.User) bool { + return u.GetLogin() == login + }) +} + +// WithCount specifies the number of required reviews. +// By default, this is 1. +func (r *ReviewByTeamMembersRequirement) WithCount(n uint) *ReviewByTeamMembersRequirement { + if n < 1 { + panic("number of required reviews should be at least 1") + } + r.count = n + return r +} + +// WithDesiredState asserts that the reviews should also be of the given ReviewState. +// +// If an empty string is passed, then all reviews are counted. This is the default. +func (r *ReviewByTeamMembersRequirement) WithDesiredState(s utils.ReviewState) *ReviewByTeamMembersRequirement { + if s != "" && !s.Valid() { + panic(fmt.Sprintf("invalid state: %q", s)) + } + r.desiredState = string(s) + return r +} + +// ReviewByTeamMembers specifies that the given pull request should receive at +// least one review from a member of the given team. +// +// The number of required reviews, or the state of the reviews (e.g., to filter +// only for approval reviews) can be modified using WithCount and WithDesiredState. +func ReviewByTeamMembers(gh *client.GitHub, team string) *ReviewByTeamMembersRequirement { + return &ReviewByTeamMembersRequirement{ + gh: gh, + team: team, + count: 1, + } +} + +// ReviewByOrgMembersRequirement asserts that the given PR has been reviewed by +// at least count members of the given organization, filtering for PR reviews +// with state desiredState. +type ReviewByOrgMembersRequirement struct { + gh *client.GitHub + count uint + desiredState string +} + +var _ Requirement = &ReviewByOrgMembersRequirement{} + +// IsSatisfied implements [Requirement]. +func (r *ReviewByOrgMembersRequirement) IsSatisfied(pr *github.PullRequest, details treeprint.Tree) bool { + detail := fmt.Sprintf("At least %d user(s) of the organization reviewed the pull request", r.count) + if r.desiredState != "" { + detail += fmt.Sprintf(" (with state %q)", r.desiredState) + } + + // Check how many members of this team already reviewed this PR. + reviewed := uint(0) reviews, err := r.gh.ListPRReviews(pr.GetNumber()) if err != nil { - r.gh.Logger.Errorf("unable to check if a member of team %s already approved this PR: %v", r.team, err) + r.gh.Logger.Errorf("unable to check number of reviews on this PR: %v", err) return utils.AddStatusNode(false, detail, details) } + reviews = deduplicateReviews(reviews) for _, review := range reviews { - teamMembers, err := r.gh.ListTeamMembers(r.team) - if err != nil { - r.gh.Logger.Errorf(err.Error()) - continue - } - - for _, member := range teamMembers { - if review.GetUser().GetLogin() == member.GetLogin() { - if review.GetState() == "APPROVED" { - approved += 1 - } - r.gh.Logger.Debugf("Member %s from team %s already reviewed PR %d with state %s (%d/%d required approval(s))", member.GetLogin(), r.team, pr.GetNumber(), review.GetState(), approved, r.count) + if review.GetAuthorAssociation() == "MEMBER" { + if r.desiredState == "" || review.GetState() == r.desiredState { + reviewed++ } + r.gh.Logger.Debugf( + "Member %s already reviewed PR %d with state %s (%d/%d required reviews with state %q)", + review.GetUser().GetLogin(), pr.GetNumber(), review.GetState(), + reviewed, r.count, r.desiredState, + ) } } - return utils.AddStatusNode(approved >= r.count, detail, details) + return utils.AddStatusNode(reviewed >= r.count, detail, details) +} + +// WithCount specifies the number of required reviews. +// By default, this is 1. +func (r *ReviewByOrgMembersRequirement) WithCount(n uint) *ReviewByOrgMembersRequirement { + if n < 1 { + panic("number of required reviews should be at least 1") + } + r.count = n + return r +} + +// WithDesiredState asserts that the reviews should also be of the given ReviewState. +// +// If an empty string is passed, then all reviews are counted. This is the default. +func (r *ReviewByOrgMembersRequirement) WithDesiredState(s utils.ReviewState) *ReviewByOrgMembersRequirement { + if s != "" && !s.Valid() { + panic(fmt.Sprintf("invalid state: %q", s)) + } + r.desiredState = string(s) + return r } -func ReviewByTeamMembers(gh *client.GitHub, team string, count uint) Requirement { - return &reviewByTeamMembers{gh, team, count} +// ReviewByOrgMembers asserts that at least 1 member of the organization +// reviewed this PR. +func ReviewByOrgMembers(gh *client.GitHub) *ReviewByOrgMembersRequirement { + return &ReviewByOrgMembersRequirement{gh: gh, count: 1} } diff --git a/contribs/github-bot/internal/requirements/reviewer_test.go b/contribs/github-bot/internal/requirements/reviewer_test.go index 16c50e13743..dac285b6b9f 100644 --- a/contribs/github-bot/internal/requirements/reviewer_test.go +++ b/contribs/github-bot/internal/requirements/reviewer_test.go @@ -16,6 +16,78 @@ import ( "github.com/xlab/treeprint" ) +func Test_deduplicateReviews(t *testing.T) { + tests := []struct { + name string + reviews []*github.PullRequestReview + expected []*github.PullRequestReview + }{ + { + name: "three different authors", + reviews: []*github.PullRequestReview{ + {User: &github.User{Login: github.String("user1")}, State: github.String("APPROVED")}, + {User: &github.User{Login: github.String("user2")}, State: github.String("CHANGES_REQUESTED")}, + {User: &github.User{Login: github.String("user3")}, State: github.String("COMMENTED")}, + }, + expected: []*github.PullRequestReview{ + {User: &github.User{Login: github.String("user1")}, State: github.String("APPROVED")}, + {User: &github.User{Login: github.String("user2")}, State: github.String("CHANGES_REQUESTED")}, + {User: &github.User{Login: github.String("user3")}, State: github.String("COMMENTED")}, + }, + }, + { + name: "single author - approval then comment", + reviews: []*github.PullRequestReview{ + {User: &github.User{Login: github.String("user1")}, State: github.String("APPROVED")}, + {User: &github.User{Login: github.String("user1")}, State: github.String("COMMENTED")}, + }, + expected: []*github.PullRequestReview{ + {User: &github.User{Login: github.String("user1")}, State: github.String("APPROVED")}, + }, + }, + { + name: "single author - approval then changes requested", + reviews: []*github.PullRequestReview{ + {User: &github.User{Login: github.String("user1")}, State: github.String("APPROVED")}, + {User: &github.User{Login: github.String("user1")}, State: github.String("CHANGES_REQUESTED")}, + }, + expected: []*github.PullRequestReview{ + {User: &github.User{Login: github.String("user1")}, State: github.String("CHANGES_REQUESTED")}, + }, + }, + { + name: "two authors - mixed reviews", + reviews: []*github.PullRequestReview{ + {User: &github.User{Login: github.String("userA")}, State: github.String("APPROVED")}, + {User: &github.User{Login: github.String("userB")}, State: github.String("CHANGES_REQUESTED")}, + {User: &github.User{Login: github.String("userA")}, State: github.String("CHANGES_REQUESTED")}, + {User: &github.User{Login: github.String("userB")}, State: github.String("COMMENTED")}, + }, + expected: []*github.PullRequestReview{ + {User: &github.User{Login: github.String("userA")}, State: github.String("CHANGES_REQUESTED")}, + {User: &github.User{Login: github.String("userB")}, State: github.String("CHANGES_REQUESTED")}, + }, + }, + { + name: "two authors - approval/changes requested then dismissed", + reviews: []*github.PullRequestReview{ + {User: &github.User{Login: github.String("user1")}, State: github.String("APPROVED")}, + {User: &github.User{Login: github.String("user1")}, State: github.String("DISMISSED")}, + {User: &github.User{Login: github.String("user2")}, State: github.String("CHANGES_REQUESTED")}, + {User: &github.User{Login: github.String("user2")}, State: github.String("DISMISSED")}, + }, + expected: []*github.PullRequestReview{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := deduplicateReviews(tt.reviews) + assert.Equal(t, tt.expected, result) + }) + } +} + func TestReviewByUser(t *testing.T) { t.Parallel() @@ -34,9 +106,13 @@ func TestReviewByUser(t *testing.T) { }, { User: &github.User{Login: github.String("user")}, State: github.String("APPROVED"), + }, { + // Should be ignored in favour of the following one + User: &github.User{Login: github.String("anotherOne")}, + State: github.String("APPROVED"), }, { User: &github.User{Login: github.String("anotherOne")}, - State: github.String("REQUEST_CHANGES"), + State: github.String("CHANGES_REQUESTED"), }, } @@ -87,7 +163,7 @@ func TestReviewByUser(t *testing.T) { pr := &github.PullRequest{} details := treeprint.New() - requirement := ReviewByUser(gh, testCase.user) + requirement := ReviewByUser(gh, testCase.user).WithDesiredState(utils.ReviewStateApproved) assert.Equal(t, requirement.IsSatisfied(pr, details), testCase.isSatisfied, fmt.Sprintf("requirement should have a satisfied status: %t", testCase.isSatisfied)) assert.True(t, utils.TestLastNodeStatus(t, testCase.isSatisfied, details), fmt.Sprintf("requirement details should have a status: %t", testCase.isSatisfied)) @@ -99,13 +175,25 @@ func TestReviewByUser(t *testing.T) { func TestReviewByTeamMembers(t *testing.T) { t.Parallel() - reviewers := github.Reviewers{ - Teams: []*github.Team{ - {Slug: github.String("team1")}, - {Slug: github.String("team2")}, - {Slug: github.String("team3")}, - }, - } + var ( + reviewers = github.Reviewers{ + Teams: []*github.Team{ + {Slug: github.String("team1")}, + {Slug: github.String("team2")}, + {Slug: github.String("team3")}, + }, + } + noReviewers = github.Reviewers{} + userReviewers = github.Reviewers{ + Users: []*github.User{ + {Login: github.String("user1")}, + {Login: github.String("user2")}, + {Login: github.String("user3")}, + {Login: github.String("user4")}, + {Login: github.String("user5")}, + }, + } + ) members := map[string][]*github.User{ "team1": { @@ -126,6 +214,10 @@ func TestReviewByTeamMembers(t *testing.T) { reviews := []*github.PullRequestReview{ { + // only later review should be counted. + User: &github.User{Login: github.String("user1")}, + State: github.String("CHANGES_REQUESTED"), + }, { User: &github.User{Login: github.String("user1")}, State: github.String("APPROVED"), }, { @@ -136,26 +228,132 @@ func TestReviewByTeamMembers(t *testing.T) { State: github.String("APPROVED"), }, { User: &github.User{Login: github.String("user4")}, - State: github.String("REQUEST_CHANGES"), + State: github.String("CHANGES_REQUESTED"), + }, { + // only later review should be counted. + User: &github.User{Login: github.String("user5")}, + State: github.String("APPROVED"), }, { User: &github.User{Login: github.String("user5")}, - State: github.String("REQUEST_CHANGES"), + State: github.String("CHANGES_REQUESTED"), }, } + const ( + notSatisfied = 0 + satisfied = 1 + withRequest = 2 + ) + for _, testCase := range []struct { - name string - team string - count uint - isSatisfied bool - testRequest bool + name string + req *ReviewByTeamMembersRequirement + reviews []*github.PullRequestReview + reviewers github.Reviewers + expectedResult byte }{ - {"3/3 team members approved;", "team1", 3, true, false}, - {"1/1 team member approved", "team2", 1, true, false}, - {"1/2 team member approved", "team2", 2, false, false}, - {"0/1 team member approved", "team3", 1, false, false}, - {"0/1 team member approved with request", "team3", 1, false, true}, - {"team doesn't exist with request", "team4", 1, false, true}, + { + name: "3/3 team members approved", + req: ReviewByTeamMembers(nil, "team1"). + WithCount(3). + WithDesiredState(utils.ReviewStateApproved), + reviews: reviews, + reviewers: reviewers, + expectedResult: satisfied, + }, + { + name: "3/3 team members approved (with user reviewers)", + req: ReviewByTeamMembers(nil, "team1"). + WithCount(3). + WithDesiredState(utils.ReviewStateApproved), + reviews: reviews, + reviewers: userReviewers, + expectedResult: satisfied, + }, + { + name: "1/1 team member approved", + req: ReviewByTeamMembers(nil, "team2"). + WithDesiredState(utils.ReviewStateApproved), + reviews: reviews, + reviewers: reviewers, + expectedResult: satisfied, + }, + { + name: "1/2 team member approved", + req: ReviewByTeamMembers(nil, "team2"). + WithCount(2). + WithDesiredState(utils.ReviewStateApproved), + reviews: reviews, + reviewers: reviewers, + expectedResult: notSatisfied, + }, + { + name: "0/1 team member approved", + req: ReviewByTeamMembers(nil, "team3"). + WithDesiredState(utils.ReviewStateApproved), + reviews: reviews, + reviewers: reviewers, + expectedResult: notSatisfied, + }, + { + name: "0/1 team member reviewed with request", + req: ReviewByTeamMembers(nil, "team3"), + // Show there are no current reviews, so we actually perform the request. + reviewers: noReviewers, + expectedResult: notSatisfied | withRequest, + }, + { + name: "3/3 team member approved from review list", + req: ReviewByTeamMembers(nil, "team1"). + WithDesiredState(utils.ReviewStateApproved). + WithCount(3), + reviews: reviews, + reviewers: noReviewers, + expectedResult: satisfied, + }, + { + name: "1/2 team member approved from review list", + req: ReviewByTeamMembers(nil, "team3"). + WithDesiredState(utils.ReviewStateApproved). + WithCount(2), + reviews: reviews, + reviewers: noReviewers, + expectedResult: notSatisfied, + }, + { + name: "team doesn't exist with request", + req: ReviewByTeamMembers(nil, "team4"). + WithDesiredState(utils.ReviewStateApproved), + reviews: reviews, + reviewers: noReviewers, + expectedResult: notSatisfied | withRequest, + }, + { + name: "3/3 team members reviewed", + req: ReviewByTeamMembers(nil, "team2"). + WithCount(3), + reviews: reviews, + reviewers: reviewers, + expectedResult: satisfied, + }, + { + name: "2/2 team members rejected", + req: ReviewByTeamMembers(nil, "team2"). + WithCount(2). + WithDesiredState(utils.ReviewStateChangesRequested), + reviews: reviews, + reviewers: reviewers, + expectedResult: satisfied, + }, + { + name: "1/3 team members approved", + req: ReviewByTeamMembers(nil, "team2"). + WithCount(3). + WithDesiredState(utils.ReviewStateApproved), + reviews: reviews, + reviewers: reviewers, + expectedResult: notSatisfied, + }, } { t.Run(testCase.name, func(t *testing.T) { t.Parallel() @@ -170,11 +368,7 @@ func TestReviewByTeamMembers(t *testing.T) { }, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { if firstRequest { - if testCase.testRequest { - w.Write(mock.MustMarshal(github.Reviewers{})) - } else { - w.Write(mock.MustMarshal(reviewers)) - } + w.Write(mock.MustMarshal(testCase.reviewers)) firstRequest = false } else { requested = true @@ -183,11 +377,92 @@ func TestReviewByTeamMembers(t *testing.T) { ), mock.WithRequestMatchPages( mock.EndpointPattern{ - Pattern: fmt.Sprintf("/orgs/teams/%s/members", testCase.team), + Pattern: fmt.Sprintf("/orgs/teams/%s/members", testCase.req.team), + Method: "GET", + }, + members[testCase.req.team], + ), + mock.WithRequestMatchPages( + mock.EndpointPattern{ + Pattern: "/repos/pulls/0/reviews", Method: "GET", }, - members[testCase.team], + testCase.reviews, ), + ) + + gh := &client.GitHub{ + Client: github.NewClient(mockedHTTPClient), + Ctx: context.Background(), + Logger: logger.NewNoopLogger(), + } + + pr := &github.PullRequest{} + details := treeprint.New() + req := new(ReviewByTeamMembersRequirement) + *req = *testCase.req + req.gh = gh + + expSatisfied := testCase.expectedResult&satisfied > 0 + expRequested := testCase.expectedResult&withRequest > 0 + assert.Equal(t, expSatisfied, req.IsSatisfied(pr, details), + "requirement should have a satisfied status: %t", expSatisfied) + assert.True(t, utils.TestLastNodeStatus(t, expSatisfied, details), + "requirement details should have a status: %t", expSatisfied) + assert.Equal(t, expRequested, requested, + "requirement should have requested to create item: %t", expRequested) + }) + } +} + +func TestReviewByOrgMembers(t *testing.T) { + t.Parallel() + + reviews := []*github.PullRequestReview{ + { + User: &github.User{Login: github.String("user1")}, + State: github.String("APPROVED"), + AuthorAssociation: github.String("MEMBER"), + }, { + // should be ignored in favour of the following one. + User: &github.User{Login: github.String("user2")}, + State: github.String("CHANGES_REQUESTED"), + AuthorAssociation: github.String("COLLABORATOR"), + }, { + User: &github.User{Login: github.String("user2")}, + State: github.String("APPROVED"), + AuthorAssociation: github.String("COLLABORATOR"), + }, { + User: &github.User{Login: github.String("user3")}, + State: github.String("APPROVED"), + AuthorAssociation: github.String("MEMBER"), + }, { + User: &github.User{Login: github.String("user4")}, + State: github.String("CHANGES_REQUESTED"), + AuthorAssociation: github.String("MEMBER"), + }, { + User: &github.User{Login: github.String("user5")}, + State: github.String("CHANGES_REQUESTED"), + AuthorAssociation: github.String("NONE"), + }, + } + + for _, testCase := range []struct { + name string + count uint + desiredState utils.ReviewState + isSatisfied bool + }{ + {"2/3 org members approved", 3, utils.ReviewStateApproved, false}, + {"2/2 org members approved", 2, utils.ReviewStateApproved, true}, + {"2/1 org members approved", 1, utils.ReviewStateApproved, true}, + {"3/3 org members reviewed", 3, "", true}, + {"3/4 org members reviewed", 4, "", false}, + } { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + mockedHTTPClient := mock.NewMockedHTTPClient( mock.WithRequestMatchPages( mock.EndpointPattern{ Pattern: "/repos/pulls/0/reviews", @@ -205,11 +480,12 @@ func TestReviewByTeamMembers(t *testing.T) { pr := &github.PullRequest{} details := treeprint.New() - requirement := ReviewByTeamMembers(gh, testCase.team, testCase.count) + requirement := ReviewByOrgMembers(gh). + WithCount(testCase.count). + WithDesiredState(testCase.desiredState) assert.Equal(t, requirement.IsSatisfied(pr, details), testCase.isSatisfied, fmt.Sprintf("requirement should have a satisfied status: %t", testCase.isSatisfied)) assert.True(t, utils.TestLastNodeStatus(t, testCase.isSatisfied, details), fmt.Sprintf("requirement details should have a status: %t", testCase.isSatisfied)) - assert.Equal(t, testCase.testRequest, requested, fmt.Sprintf("requirement should have requested to create item: %t", testCase.testRequest)) }) } } diff --git a/contribs/github-bot/internal/utils/github_const.go b/contribs/github-bot/internal/utils/github_const.go index f030d9365f7..7f64cb736ea 100644 --- a/contribs/github-bot/internal/utils/github_const.go +++ b/contribs/github-bot/internal/utils/github_const.go @@ -13,3 +13,24 @@ const ( PRStateOpen = "open" PRStateClosed = "closed" ) + +// ReviewState is the state of a PR review. See: +// https://docs.github.com/en/graphql/reference/enums#pullrequestreviewstate +type ReviewState string + +// Possible values of ReviewState. +const ( + ReviewStateApproved ReviewState = "APPROVED" + ReviewStateChangesRequested ReviewState = "CHANGES_REQUESTED" + ReviewStateCommented ReviewState = "COMMENTED" + ReviewStateDismissed ReviewState = "DISMISSED" +) + +// Valid determines whether the ReviewState is one of the known ReviewStates. +func (r ReviewState) Valid() bool { + switch r { + case ReviewStateApproved, ReviewStateChangesRequested, ReviewStateCommented: + return true + } + return false +} diff --git a/contribs/github-bot/internal/utils/testing.go b/contribs/github-bot/internal/utils/testing.go index 3c7f7bfef88..839bde831dd 100644 --- a/contribs/github-bot/internal/utils/testing.go +++ b/contribs/github-bot/internal/utils/testing.go @@ -9,8 +9,13 @@ import ( func TestLastNodeStatus(t *testing.T, success bool, details treeprint.Tree) bool { t.Helper() + return TestNodeStatus(t, success, details.FindLastNode()) +} + +func TestNodeStatus(t *testing.T, success bool, details treeprint.Tree) bool { + t.Helper() - detail := details.FindLastNode().(*treeprint.Node).Value.(string) + detail := details.(*treeprint.Node).Value.(string) status := Fail if success { diff --git a/contribs/gnodev/cmd/gnobro/main.go b/contribs/gnodev/cmd/gnobro/main.go index 91713d6c6d8..7e47fb96780 100644 --- a/contribs/gnodev/cmd/gnobro/main.go +++ b/contribs/gnodev/cmd/gnobro/main.go @@ -11,7 +11,7 @@ import ( "net/url" "os" "os/signal" - "path/filepath" + gopath "path" "strings" "time" @@ -239,7 +239,6 @@ func runLocal(ctx context.Context, gnocl *gnoclient.Client, cfg *broCfg, bcfg br ) var errgs errgroup.Group - if cfg.dev { devpoint, err := getDevEndpoint(cfg) if err != nil { @@ -453,7 +452,7 @@ func ValidatePathCommandMiddleware(pathPrefix string) wish.Middleware { return case 1: // check for valid path path := cmd[0] - if strings.HasPrefix(path, pathPrefix) && filepath.Clean(path) == path { + if strings.HasPrefix(path, pathPrefix) && gopath.Clean(path) == path { s.Context().SetValue("path", path) next(s) return diff --git a/contribs/gnodev/cmd/gnodev/accounts.go b/contribs/gnodev/cmd/gnodev/accounts.go index 95c2c3efffc..e148f4827c1 100644 --- a/contribs/gnodev/cmd/gnodev/accounts.go +++ b/contribs/gnodev/cmd/gnodev/accounts.go @@ -49,7 +49,7 @@ func (va varPremineAccounts) String() string { return strings.Join(accs, ",") } -func generateBalances(bk *address.Book, cfg *devCfg) (gnoland.Balances, error) { +func generateBalances(bk *address.Book, cfg *AppConfig) (gnoland.Balances, error) { bls := gnoland.NewBalances() premineBalance := std.Coins{std.NewCoin(ugnot.Denom, 10e12)} diff --git a/contribs/gnodev/cmd/gnodev/app.go b/contribs/gnodev/cmd/gnodev/app.go new file mode 100644 index 00000000000..5744be8d0b4 --- /dev/null +++ b/contribs/gnodev/cmd/gnodev/app.go @@ -0,0 +1,582 @@ +package main + +import ( + "context" + "fmt" + "io" + "log/slog" + "net/http" + "os" + "path/filepath" + "slices" + "strings" + "time" + + "github.com/gnolang/gno/contribs/gnodev/pkg/address" + gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev" + "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" + "github.com/gnolang/gno/contribs/gnodev/pkg/packages" + "github.com/gnolang/gno/contribs/gnodev/pkg/proxy" + "github.com/gnolang/gno/contribs/gnodev/pkg/rawterm" + "github.com/gnolang/gno/contribs/gnodev/pkg/watcher" + "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" + osm "github.com/gnolang/gno/tm2/pkg/os" +) + +const ( + DefaultDeployerName = integration.DefaultAccount_Name + DefaultDeployerSeed = integration.DefaultAccount_Seed +) + +var defaultDeployerAddress = crypto.MustAddressFromString(integration.DefaultAccount_Address) + +const ( + NodeLogName = "Node" + WebLogName = "GnoWeb" + KeyPressLogName = "KeyPress" + EventServerLogName = "Event" + AccountsLogName = "Accounts" + LoaderLogName = "Loader" + ProxyLogName = "Proxy" +) + +type App struct { + io commands.IO + start time.Time // Time when the server started + cfg *AppConfig + logger *slog.Logger + pathManager *pathManager + // Contains all the deferred functions of the app. + // Will be triggered on close for cleanup. + deferred func() + + webHomePath string + paths []string + devNode *gnodev.Node + emitterServer *emitter.Server + watcher *watcher.PackageWatcher + loader packages.Loader + book *address.Book + exportPath string + proxy *proxy.PathInterceptor + + // XXX: move this + exported uint +} + +func runApp(cfg *AppConfig, cio commands.IO, dirs ...string) (err error) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var rt *rawterm.RawTerm + var out io.Writer + if cfg.interactive { + var restore func() error + rt, restore, err = setupRawTerm(cfg, cio) + if err != nil { + return fmt.Errorf("unable to init raw term: %w", err) + } + defer restore() + + osm.TrapSignal(func() { + cancel() + restore() + }) + + out = rt + } else { + osm.TrapSignal(cancel) + out = cio.Out() + } + + logger, err := setuplogger(cfg, out) + if err != nil { + return fmt.Errorf("unable to setup logger: %w", err) + } + + app := NewApp(logger, cfg, cio) + if err := app.Setup(ctx, dirs...); err != nil { + return err + } + defer app.Close() + + if rt != nil { + go func() { + app.RunInteractive(ctx, rt) + cancel() + }() + } + + return app.RunServer(ctx, rt) +} + +func NewApp(logger *slog.Logger, cfg *AppConfig, io commands.IO) *App { + return &App{ + start: time.Now(), + deferred: func() {}, + logger: logger, + cfg: cfg, + io: io, + pathManager: newPathManager(), + } +} + +func (ds *App) Defer(fn func()) { + old := ds.deferred + ds.deferred = func() { + defer old() + fn() + } +} + +func (ds *App) DeferClose(fn func() error) { + ds.Defer(func() { + if err := fn(); err != nil { + ds.logger.Debug("close", "error", err.Error()) + } + }) +} + +func (ds *App) Close() { + ds.deferred() +} + +func (ds *App) Setup(ctx context.Context, dirs ...string) (err error) { + if err := ds.cfg.validateConfigFlags(); err != nil { + return fmt.Errorf("validate error: %w", err) + } + + loggerEvents := ds.logger.WithGroup(EventServerLogName) + ds.emitterServer = emitter.NewServer(loggerEvents) + + // XXX: it would be nice to not have this hardcoded + examplesDir := filepath.Join(ds.cfg.root, "examples") + + // Setup loader and resolver + loaderLogger := ds.logger.WithGroup(LoaderLogName) + resolver, localPaths := setupPackagesResolver(loaderLogger, ds.cfg, dirs...) + ds.loader = packages.NewGlobLoader(examplesDir, resolver) + + // Get user's address book from local keybase + accountLogger := ds.logger.WithGroup(AccountsLogName) + ds.book, err = setupAddressBook(accountLogger, ds.cfg) + if err != nil { + return fmt.Errorf("unable to load keybase: %w", err) + } + + // Generate user's paths using a comma as the delimiter + qpaths := strings.Split(ds.cfg.paths, ",") + + // Set up the packages modifier and extract paths from queries + // XXX: This should probably be moved into the setup node configuration + modifiers, paths, err := resolvePackagesModifier(ds.cfg, ds.book, qpaths) + if err != nil { + return fmt.Errorf("unable to resolve paths %v: %w", paths, err) + } + + // Add the user's paths to the pre-loaded paths + // Modifiers will be added later to the node config bellow + ds.paths = append(paths, localPaths...) + + // Setup default web home realm, fallback on first local path + switch webHome := ds.cfg.webHome; webHome { + case "": + if len(ds.paths) > 0 { + ds.webHomePath = strings.TrimPrefix(ds.paths[0], ds.cfg.chainDomain) + ds.logger.WithGroup(WebLogName).Info("using default package", "path", ds.paths[0]) + } + case "/", ":none:": // skip + default: + ds.webHomePath = webHome + } + + balances, err := generateBalances(ds.book, ds.cfg) + if err != nil { + return fmt.Errorf("unable to generate balances: %w", err) + } + ds.logger.Debug("balances loaded", "list", balances.List()) + + nodeLogger := ds.logger.WithGroup(NodeLogName) + nodeCfg := setupDevNodeConfig(ds.cfg, nodeLogger, ds.emitterServer, balances, ds.loader) + nodeCfg.PackagesModifier = modifiers // add modifiers + + address := resolveUnixOrTCPAddr(nodeCfg.TMConfig.RPC.ListenAddress) + + // Setup lazy proxy + if ds.cfg.lazyLoader { + proxyLogger := ds.logger.WithGroup(ProxyLogName) + ds.proxy, err = proxy.NewPathInterceptor(proxyLogger, address) + if err != nil { + return fmt.Errorf("unable to setup proxy: %w", err) + } + ds.DeferClose(ds.proxy.Close) + + // Override current rpc listener + nodeCfg.TMConfig.RPC.ListenAddress = ds.proxy.ProxyAddress() + proxyLogger.Debug("proxy started", + "proxy_addr", ds.proxy.ProxyAddress(), + "target_addr", ds.proxy.TargetAddress(), + ) + + proxyLogger.Info("lazy loading is enabled. packages will be loaded only upon a request via a query or transaction.", "loader", ds.loader.Name()) + } else { + nodeCfg.TMConfig.RPC.ListenAddress = fmt.Sprintf("%s://%s", address.Network(), address.String()) + } + + ds.devNode, err = setupDevNode(ctx, ds.cfg, nodeCfg, ds.paths...) + if err != nil { + return err + } + ds.DeferClose(ds.devNode.Close) + + ds.watcher, err = watcher.NewPackageWatcher(loggerEvents, ds.emitterServer) + if err != nil { + return fmt.Errorf("unable to setup packages watcher: %w", err) + } + + ds.watcher.UpdatePackagesWatch(ds.devNode.ListPkgs()...) + + return nil +} + +func (ds *App) setupHandlers(ctx context.Context) (http.Handler, error) { + mux := http.NewServeMux() + remote := ds.devNode.GetRemoteAddress() + + if ds.proxy != nil { + proxyLogger := ds.logger.WithGroup(ProxyLogName) + remote = ds.proxy.TargetAddress() // update remote address with proxy target address + + // Generate initial paths + initPaths := map[string]struct{}{} + for _, pkg := range ds.devNode.ListPkgs() { + initPaths[pkg.Path] = struct{}{} + } + + ds.proxy.HandlePath(func(paths ...string) { + newPath := false + for _, path := range paths { + // Check if the path is an initial path. + if _, ok := initPaths[path]; ok { + continue + } + + // Try to resolve the path first. + // If we are unable to resolve it, ignore and continue + + if _, err := ds.loader.Resolve(path); err != nil { + proxyLogger.Debug("unable to resolve path", + "error", err, + "path", path) + continue + } + + // If we already know this path, continue. + if exist := ds.pathManager.Save(path); exist { + continue + } + + proxyLogger.Info("new monitored path", + "path", path) + + newPath = true + } + + if !newPath { + return + } + + ds.emitterServer.LockEmit() + defer ds.emitterServer.UnlockEmit() + + ds.devNode.SetPackagePaths(ds.paths...) + ds.devNode.AddPackagePaths(ds.pathManager.List()...) + + // Check if the node needs to be reloaded + // XXX: This part can likely be optimized if we believe + // it significantly impacts performance. + for _, path := range paths { + if ds.devNode.HasPackageLoaded(path) { + continue + } + + ds.logger.WithGroup(NodeLogName).Debug("some paths aren't loaded yet", "path", path) + + // If the package isn't loaded, attempt to reload the node + if err := ds.devNode.Reload(ctx); err != nil { + ds.logger.WithGroup(NodeLogName).Error("unable to reload node", "err", err) + } + + // Update the watcher list with the currently loaded packages + ds.watcher.UpdatePackagesWatch(ds.devNode.ListPkgs()...) + + // Reloading the node once is sufficient, so exit the loop + return + } + + ds.logger.WithGroup(NodeLogName).Debug("paths already loaded, skipping reload", "paths", paths) + }) + } + + // Setup gnoweb + webhandler, err := setupGnoWebServer(ds.logger.WithGroup(WebLogName), ds.cfg, remote) + if err != nil { + return nil, fmt.Errorf("unable to setup gnoweb server: %w", err) + } + + if ds.webHomePath != "" { + serveWeb := webhandler.ServeHTTP + webhandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "" || r.URL.Path == "/" { + http.Redirect(w, r, ds.webHomePath, http.StatusFound) + } else { + serveWeb(w, r) + } + }) + } + + // Setup unsafe API + if ds.cfg.unsafeAPI { + mux.HandleFunc("/reset", func(res http.ResponseWriter, req *http.Request) { + if err := ds.devNode.Reset(req.Context()); err != nil { + ds.logger.Error("failed to reset", slog.Any("err", err)) + res.WriteHeader(http.StatusInternalServerError) + } + }) + + mux.HandleFunc("/reload", func(res http.ResponseWriter, req *http.Request) { + if err := ds.devNode.Reload(req.Context()); err != nil { + ds.logger.Error("failed to reload", slog.Any("err", err)) + res.WriteHeader(http.StatusInternalServerError) + } + }) + } + + if !ds.cfg.noWatch { + evtstarget := fmt.Sprintf("%s/_events", ds.cfg.webListenerAddr) + mux.Handle("/_events", ds.emitterServer) + mux.Handle("/", emitter.NewMiddleware(evtstarget, webhandler)) + } else { + mux.Handle("/", webhandler) + } + + return mux, nil +} + +func (ds *App) RunServer(ctx context.Context, term *rawterm.RawTerm) error { + ctx, cancelWith := context.WithCancelCause(ctx) + defer cancelWith(nil) + + addr := ds.cfg.webListenerAddr + handlers, err := ds.setupHandlers(ctx) + if err != nil { + return fmt.Errorf("unable to setup handlers: %w", err) + } + + server := &http.Server{ + Handler: handlers, + Addr: addr, + ReadHeaderTimeout: 60 * time.Second, + } + + // Serve gnoweb + if !ds.cfg.noWeb { + go func() { + err := server.ListenAndServe() + cancelWith(err) + }() + + ds.logger.WithGroup(WebLogName).Info("gnoweb started", + "lisn", fmt.Sprintf("http://%s", addr)) + } + + if ds.cfg.interactive { + ds.logger.WithGroup("--- READY").Info("for commands and help, press `h`", "took", time.Since(ds.start)) + } else { + ds.logger.Info("node is ready", "took", time.Since(ds.start)) + } + + for { + select { + case <-ctx.Done(): + return context.Cause(ctx) + case _, ok := <-ds.watcher.PackagesUpdate: + if !ok { + return nil + } + + ds.logger.WithGroup(NodeLogName).Info("reloading...") + if err := ds.devNode.Reload(ctx); err != nil { + ds.logger.WithGroup(NodeLogName).Error("unable to reload node", "err", err) + } + ds.watcher.UpdatePackagesWatch(ds.devNode.ListPkgs()...) + } + } +} + +func (ds *App) RunInteractive(ctx context.Context, term *rawterm.RawTerm) { + ds.logger.WithGroup(KeyPressLogName).Debug("starting interactive mode") + var keyPressCh <-chan rawterm.KeyPress + if ds.cfg.interactive { + keyPressCh = listenForKeyPress(ds.logger.WithGroup(KeyPressLogName), term) + } + + for { + select { + case <-ctx.Done(): + return + case key, ok := <-keyPressCh: + ds.logger.WithGroup(KeyPressLogName).Debug("pressed", "key", key.String()) + if !ok { + return + } + + if key == rawterm.KeyCtrlC { + return + } + + ds.handleKeyPress(ctx, key) + keyPressCh = listenForKeyPress(ds.logger.WithGroup(KeyPressLogName), term) + } + } +} + +var helper string = `For more in-depth documentation, visit the GNO Tooling CLI documentation: +https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev + +P Previous TX - Go to the previous tx +N Next TX - Go to the next tx +E Export - Export the current state as genesis doc +A Accounts - Display known accounts and balances +H Help - Display this message +R Reload - Reload all packages to take change into account. +Ctrl+S Save State - Save the current state +Ctrl+R Reset - Reset application to it's initial/save state. +Ctrl+C Exit - Exit the application +` + +func (ds *App) handleKeyPress(ctx context.Context, key rawterm.KeyPress) { + var err error + + switch key.Upper() { + case rawterm.KeyH: // Helper + ds.logger.Info("Gno Dev Helper", "helper", helper) + + case rawterm.KeyA: // Accounts + logAccounts(ds.logger.WithGroup(AccountsLogName), ds.book, ds.devNode) + + case rawterm.KeyR: // Reload + ds.logger.WithGroup(NodeLogName).Info("reloading...") + if err = ds.devNode.ReloadAll(ctx); err != nil { + ds.logger.WithGroup(NodeLogName).Error("unable to reload node", "err", err) + } + + case rawterm.KeyCtrlR: // Reset + ds.logger.WithGroup(NodeLogName).Info("resetting node state...") + // Reset paths + ds.pathManager.Reset() + ds.devNode.SetPackagePaths(ds.paths...) + // Reset the node + if err = ds.devNode.Reset(ctx); err != nil { + ds.logger.WithGroup(NodeLogName).Error("unable to reset node state", "err", err) + } + + case rawterm.KeyCtrlS: // Save + ds.logger.WithGroup(NodeLogName).Info("saving state...") + if err := ds.devNode.SaveCurrentState(ctx); err != nil { + ds.logger.WithGroup(NodeLogName).Error("unable to save node state", "err", err) + } + + case rawterm.KeyE: // Export + // Create a temporary export dir + if ds.exported == 0 { + ds.exportPath, err = os.MkdirTemp("", "gnodev-export") + if err != nil { + ds.logger.WithGroup(NodeLogName).Error("unable to create `export` directory", "err", err) + return + } + } + ds.exported++ + + ds.logger.WithGroup(NodeLogName).Info("exporting state...") + doc, err := ds.devNode.ExportStateAsGenesis(ctx) + if err != nil { + ds.logger.WithGroup(NodeLogName).Error("unable to export node state", "err", err) + return + } + + docfile := filepath.Join(ds.exportPath, fmt.Sprintf("export_%d.jsonl", ds.exported)) + if err := doc.SaveAs(docfile); err != nil { + ds.logger.WithGroup(NodeLogName).Error("unable to save genesis", "err", err) + } + + ds.logger.WithGroup(NodeLogName).Info("node state exported", "file", docfile) + + case rawterm.KeyN: // Next tx + ds.logger.Info("moving forward...") + if err := ds.devNode.MoveToNextTX(ctx); err != nil { + ds.logger.WithGroup(NodeLogName).Error("unable to move forward", "err", err) + } + + case rawterm.KeyP: // Previous tx + ds.logger.Info("moving backward...") + if err := ds.devNode.MoveToPreviousTX(ctx); err != nil { + ds.logger.WithGroup(NodeLogName).Error("unable to move backward", "err", err) + } + default: + } +} + +// XXX: packages modifier does not support glob yet +func resolvePackagesModifier(cfg *AppConfig, bk *address.Book, qpaths []string) ([]gnodev.QueryPath, []string, error) { + if cfg.deployKey == "" { + return nil, nil, fmt.Errorf("default deploy key cannot be empty") + } + + defaultKey, _, ok := bk.GetFromNameOrAddress(cfg.deployKey) + if !ok { + return nil, nil, fmt.Errorf("unable to get deploy key %q", cfg.deployKey) + } + + modifiers := make([]gnodev.QueryPath, 0, len(qpaths)) + paths := make([]string, 0, len(qpaths)) + + for _, path := range qpaths { + if path == "" { + continue + } + + qpath, err := gnodev.ResolveQueryPath(bk, path) + if err != nil { + return nil, nil, fmt.Errorf("invalid package path/query %q: %w", path, err) + } + + // Assign a default creator if user haven't specified it. + if qpath.Creator.IsZero() { + qpath.Creator = defaultKey + } + + modifiers = append(modifiers, qpath) + paths = append(paths, qpath.Path) + } + + return slices.Clip(modifiers), slices.Clip(paths), nil +} + +func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.KeyPress { + cc := make(chan rawterm.KeyPress, 1) + go func() { + defer close(cc) + key, err := rt.ReadKeyPress() + if err != nil { + logger.Error("unable to read keypress", "err", err) + return + } + + cc <- key + }() + + return cc +} diff --git a/contribs/gnodev/cmd/gnodev/app_config.go b/contribs/gnodev/cmd/gnodev/app_config.go new file mode 100644 index 00000000000..07231f24f9b --- /dev/null +++ b/contribs/gnodev/cmd/gnodev/app_config.go @@ -0,0 +1,237 @@ +package main + +import "flag" + +type AppConfig struct { + // Listeners + nodeRPCListenerAddr string + nodeP2PListenerAddr string + nodeProxyAppListenerAddr string + + // Users default + deployKey string + home string + root string + premineAccounts varPremineAccounts + + // Files + balancesFile string + genesisFile string + txsFile string + + // Web Configuration + noWeb bool + webHTML bool + webListenerAddr string + webRemoteHelperAddr string + webWithHTML bool + webHome string + + // Resolver + resolvers varResolver + + // Node Configuration + logFormat string + lazyLoader bool + verbose bool + noWatch bool + noReplay bool + maxGas int64 + chainId string + chainDomain string + unsafeAPI bool + interactive bool + paths string +} + +func (c *AppConfig) RegisterFlagsWith(fs *flag.FlagSet, defaultCfg AppConfig) { + *c = defaultCfg // Copy default config + + fs.StringVar( + &c.home, + "home", + defaultCfg.home, + "user's local directory for keys", + ) + + fs.BoolVar( + &c.interactive, + "interactive", + defaultCfg.interactive, + "enable gnodev interactive mode", + ) + + fs.StringVar( + &c.root, + "root", + defaultCfg.root, + "gno root directory", + ) + + fs.BoolVar( + &c.noWeb, + "no-web", + defaultLocalAppConfig.noWeb, + "disable gnoweb", + ) + + fs.BoolVar( + &c.webHTML, + "web-html", + defaultLocalAppConfig.webHTML, + "gnoweb: enable unsafe HTML parsing in markdown rendering", + ) + + fs.StringVar( + &c.webListenerAddr, + "web-listener", + defaultCfg.webListenerAddr, + "gnoweb: web server listener address", + ) + + fs.StringVar( + &c.webRemoteHelperAddr, + "web-help-remote", + defaultCfg.webRemoteHelperAddr, + "gnoweb: web server help page's remote addr (default to )", + ) + + fs.BoolVar( + &c.webWithHTML, + "web-with-html", + defaultCfg.webWithHTML, + "gnoweb: enable HTML parsing in markdown rendering", + ) + + fs.StringVar( + &c.webHome, + "web-home", + defaultCfg.webHome, + "gnoweb: set default home page, use `/` or `:none:` to use default web home redirect", + ) + + fs.Var( + &c.resolvers, + "resolver", + "list of additional resolvers (`root`, `local` or `remote`), will be executed in the given order", + ) + + fs.StringVar( + &c.nodeRPCListenerAddr, + "node-rpc-listener", + defaultCfg.nodeRPCListenerAddr, + "listening address for GnoLand RPC node", + ) + + fs.Var( + &c.premineAccounts, + "add-account", + "add (or set) a premine account in the form `[=]`, can be used multiple time", + ) + + fs.StringVar( + &c.balancesFile, + "balance-file", + defaultCfg.balancesFile, + "load the provided balance file (refer to the documentation for format)", + ) + + fs.StringVar( + &c.txsFile, + "txs-file", + defaultCfg.txsFile, + "load the provided transactions file (refer to the documentation for format)", + ) + + fs.StringVar( + &c.genesisFile, + "genesis", + defaultCfg.genesisFile, + "load the given genesis file", + ) + + fs.StringVar( + &c.deployKey, + "deploy-key", + defaultCfg.deployKey, + "default key name or Bech32 address for deploying packages", + ) + + fs.StringVar( + &c.chainId, + "chain-id", + defaultCfg.chainId, + "set node ChainID", + ) + + fs.StringVar( + &c.chainDomain, + "chain-domain", + defaultCfg.chainDomain, + "set node ChainDomain", + ) + + fs.BoolVar( + &c.noWatch, + "no-watch", + defaultCfg.noWatch, + "do not watch for file changes", + ) + + fs.BoolVar( + &c.noReplay, + "no-replay", + defaultCfg.noReplay, + "do not replay previous transactions upon reload", + ) + + fs.BoolVar( + &c.lazyLoader, + "lazy-loader", + defaultCfg.lazyLoader, + "enable lazy loader", + ) + + fs.Int64Var( + &c.maxGas, + "max-gas", + defaultCfg.maxGas, + "set the maximum gas per block", + ) + + fs.BoolVar( + &c.unsafeAPI, + "unsafe-api", + defaultCfg.unsafeAPI, + "enable /reset and /reload endpoints which are not safe to expose publicly", + ) + + fs.StringVar( + &c.logFormat, + "log-format", + defaultCfg.logFormat, + "log output format, can be `json` or `console`", + ) + + fs.StringVar( + &c.paths, + "paths", + defaultCfg.paths, + "additional path(s) to load, separated by comma", + ) + + fs.BoolVar( + &c.verbose, + "v", + defaultCfg.verbose, + "enable verbose output for development", + ) +} + +func (c *AppConfig) validateConfigFlags() error { + if (c.balancesFile != "" || c.txsFile != "") && c.genesisFile != "" { + return ErrConflictingFileArgs + } + + return nil +} diff --git a/contribs/gnodev/cmd/gnodev/command_local.go b/contribs/gnodev/cmd/gnodev/command_local.go new file mode 100644 index 00000000000..2a1ccfa063d --- /dev/null +++ b/contribs/gnodev/cmd/gnodev/command_local.go @@ -0,0 +1,114 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/gnolang/gno/contribs/gnodev/pkg/packages" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/mattn/go-isatty" +) + +const DefaultDomain = "gno.land" + +var ErrConflictingFileArgs = errors.New("cannot specify `balances-file` or `txs-file` along with `genesis-file`") + +type LocalAppConfig struct { + AppConfig + + chdir string // directory context +} + +var defaultLocalAppConfig = AppConfig{ + chainId: "dev", + logFormat: "console", + chainDomain: DefaultDomain, + maxGas: 10_000_000_000, + webListenerAddr: "127.0.0.1:8888", + nodeRPCListenerAddr: "127.0.0.1:26657", + deployKey: defaultDeployerAddress.String(), + home: gnoenv.HomeDir(), + root: gnoenv.RootDir(), + interactive: isatty.IsTerminal(os.Stdout.Fd()), + unsafeAPI: true, + lazyLoader: true, + + // As we have no reason to configure this yet, set this to random port + // to avoid potential conflict with other app + nodeP2PListenerAddr: "tcp://127.0.0.1:0", + nodeProxyAppListenerAddr: "tcp://127.0.0.1:0", +} + +func NewLocalCmd(io commands.IO) *commands.Command { + var cfg LocalAppConfig + + return commands.NewCommand( + commands.Metadata{ + Name: "local", + ShortUsage: "gnodev local [flags] [package_dir...]", + ShortHelp: "Start gnodev in local development mode (default)", + LongHelp: "LOCAL: Local mode configure the node for local development usage", + NoParentFlags: true, + }, + &cfg, + func(_ context.Context, args []string) error { + return execLocalApp(&cfg, args, io) + }, + ) +} + +func (c *LocalAppConfig) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar( + &c.chdir, + "C", + c.chdir, + "change directory context before running gnodev", + ) + + c.AppConfig.RegisterFlagsWith(fs, defaultLocalAppConfig) +} + +func execLocalApp(cfg *LocalAppConfig, args []string, cio commands.IO) error { + if cfg.chdir != "" { + if err := os.Chdir(cfg.chdir); err != nil { + return fmt.Errorf("unable to change directory: %w", err) + } + } + + dir, err := os.Getwd() + if err != nil { + return fmt.Errorf("unable to guess current dir: %w", err) + } + + // If no resolvers is defined, use gno example as root resolver + var baseResolvers []packages.Resolver + if len(cfg.resolvers) == 0 { + gnoroot, err := gnoenv.GuessRootDir() + if err != nil { + return err + } + + exampleRoot := filepath.Join(gnoroot, "examples") + baseResolvers = append(baseResolvers, packages.NewRootResolver(exampleRoot)) + } + + // Check if current directory is a valid gno package + path := guessPath(&cfg.AppConfig, dir) + resolver := packages.NewLocalResolver(path, dir) + if resolver.IsValid() { + // Add current directory as local resolver + baseResolvers = append([]packages.Resolver{resolver}, baseResolvers...) + if len(cfg.paths) > 0 { + cfg.paths += "," + } + cfg.paths += resolver.Path + } + cfg.resolvers = append(baseResolvers, cfg.resolvers...) + + return runApp(&cfg.AppConfig, cio) // else run app without any dir +} diff --git a/contribs/gnodev/cmd/gnodev/command_staging.go b/contribs/gnodev/cmd/gnodev/command_staging.go new file mode 100644 index 00000000000..d02d7c289fe --- /dev/null +++ b/contribs/gnodev/cmd/gnodev/command_staging.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + "flag" + "path" + "path/filepath" + + "github.com/gnolang/gno/contribs/gnodev/pkg/packages" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +type StagingAppConfig struct { + AppConfig +} + +var defaultStagingOptions = AppConfig{ + chainId: "dev", + chainDomain: DefaultDomain, + logFormat: "json", + maxGas: 10_000_000_000, + webHome: ":none:", + webListenerAddr: "127.0.0.1:8888", + nodeRPCListenerAddr: "127.0.0.1:26657", + deployKey: defaultDeployerAddress.String(), + home: gnoenv.HomeDir(), + root: gnoenv.RootDir(), + interactive: false, + unsafeAPI: false, + lazyLoader: false, + paths: path.Join(DefaultDomain, "/**"), // Load every package under the main domain}, + + // As we have no reason to configure this yet, set this to random port + // to avoid potential conflict with other app + nodeP2PListenerAddr: "tcp://127.0.0.1:0", + nodeProxyAppListenerAddr: "tcp://127.0.0.1:0", +} + +func NewStagingCmd(io commands.IO) *commands.Command { + var cfg StagingAppConfig + + return commands.NewCommand( + commands.Metadata{ + Name: "staging", + ShortUsage: "gnodev staging [flags] [package_dir...]", + ShortHelp: "Start gnodev in staging mode", + LongHelp: "STAGING: Staging mode configure the node for server usage", + NoParentFlags: true, + }, + &cfg, + func(_ context.Context, args []string) error { + return execStagingCmd(&cfg, args, io) + }, + ) +} + +func (c *StagingAppConfig) RegisterFlags(fs *flag.FlagSet) { + c.AppConfig.RegisterFlagsWith(fs, defaultStagingOptions) +} + +func execStagingCmd(cfg *StagingAppConfig, args []string, io commands.IO) error { + // If no resolvers is defined, use gno example as root resolver + if len(cfg.AppConfig.resolvers) == 0 { + gnoroot, err := gnoenv.GuessRootDir() + if err != nil { + return err + } + + exampleRoot := filepath.Join(gnoroot, "examples") + cfg.AppConfig.resolvers = append(cfg.AppConfig.resolvers, packages.NewRootResolver(exampleRoot)) + } + + return runApp(&cfg.AppConfig, io, args...) +} diff --git a/contribs/gnodev/cmd/gnodev/logger.go b/contribs/gnodev/cmd/gnodev/logger.go index 9e69654f478..1fbcd95e953 100644 --- a/contribs/gnodev/cmd/gnodev/logger.go +++ b/contribs/gnodev/cmd/gnodev/logger.go @@ -1,35 +1,59 @@ package main import ( + "fmt" "io" "log/slog" "github.com/charmbracelet/lipgloss" "github.com/gnolang/gno/contribs/gnodev/pkg/logger" - gnolog "github.com/gnolang/gno/gno.land/pkg/log" + "github.com/gnolang/gno/gno.land/pkg/log" "github.com/muesli/termenv" + "go.uber.org/zap/zapcore" ) -func setuplogger(cfg *devCfg, out io.Writer) *slog.Logger { +func setuplogger(cfg *AppConfig, out io.Writer) (*slog.Logger, error) { level := slog.LevelInfo if cfg.verbose { level = slog.LevelDebug } - if cfg.serverMode { - zaplogger := logger.NewZapLogger(out, level) - return gnolog.ZapLoggerToSlog(zaplogger) - } + // Set up the logger + switch cfg.logFormat { + case "json": + return newJSONLogger(out, level), nil + case "console", "": + // Detect term color profile + colorProfile := termenv.DefaultOutput().Profile + + clogger := logger.NewColumnLogger(out, level, colorProfile) + + // Register well known group color with system colors + clogger.RegisterGroupColor(NodeLogName, lipgloss.Color("3")) + clogger.RegisterGroupColor(WebLogName, lipgloss.Color("4")) + clogger.RegisterGroupColor(KeyPressLogName, lipgloss.Color("5")) + clogger.RegisterGroupColor(EventServerLogName, lipgloss.Color("6")) - // Detect term color profile - colorProfile := termenv.DefaultOutput().Profile - clogger := logger.NewColumnLogger(out, level, colorProfile) + return slog.New(clogger), nil + default: + return nil, fmt.Errorf("invalid log format %q", cfg.logFormat) + } +} - // Register well known group color with system colors - clogger.RegisterGroupColor(NodeLogName, lipgloss.Color("3")) - clogger.RegisterGroupColor(WebLogName, lipgloss.Color("4")) - clogger.RegisterGroupColor(KeyPressLogName, lipgloss.Color("5")) - clogger.RegisterGroupColor(EventServerLogName, lipgloss.Color("6")) +func newJSONLogger(w io.Writer, level slog.Level) *slog.Logger { + var zaplevel zapcore.Level + switch level { + case slog.LevelDebug: + zaplevel = zapcore.DebugLevel + case slog.LevelInfo: + zaplevel = zapcore.InfoLevel + case slog.LevelWarn: + zaplevel = zapcore.WarnLevel + case slog.LevelError: + zaplevel = zapcore.ErrorLevel + default: + panic("unknown slog level: " + level.String()) + } - return slog.New(clogger) + return log.ZapLoggerToSlog(log.NewZapJSONLogger(w, zaplevel)) } diff --git a/contribs/gnodev/cmd/gnodev/main.go b/contribs/gnodev/cmd/gnodev/main.go index 95f1d95e0a6..a14f76e9d81 100644 --- a/contribs/gnodev/cmd/gnodev/main.go +++ b/contribs/gnodev/cmd/gnodev/main.go @@ -1,586 +1,60 @@ package main import ( + "bytes" "context" "errors" "flag" "fmt" - "log/slog" - "net/http" "os" - "path/filepath" - "time" - "github.com/gnolang/gno/contribs/gnodev/pkg/address" - gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev" - "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" - "github.com/gnolang/gno/contribs/gnodev/pkg/rawterm" - "github.com/gnolang/gno/contribs/gnodev/pkg/watcher" - "github.com/gnolang/gno/gno.land/pkg/integration" - "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/crypto" - osm "github.com/gnolang/gno/tm2/pkg/os" ) -const ( - NodeLogName = "Node" - WebLogName = "GnoWeb" - KeyPressLogName = "KeyPress" - EventServerLogName = "Event" - AccountsLogName = "Accounts" -) - -var ErrConflictingFileArgs = errors.New("cannot specify `balances-file` or `txs-file` along with `genesis-file`") - -var ( - DefaultDeployerName = integration.DefaultAccount_Name - DefaultDeployerAddress = crypto.MustAddressFromString(integration.DefaultAccount_Address) - DefaultDeployerSeed = integration.DefaultAccount_Seed -) - -type devCfg struct { - // Listeners - nodeRPCListenerAddr string - nodeP2PListenerAddr string - nodeProxyAppListenerAddr string - - // Users default - deployKey string - home string - root string - premineAccounts varPremineAccounts - - // Files - balancesFile string - genesisFile string - txsFile string - - // Web Configuration - noWeb bool - webHTML bool - webListenerAddr string - webRemoteHelperAddr string - - // Node Configuration - minimal bool - verbose bool - noWatch bool - noReplay bool - maxGas int64 - chainId string - chainDomain string - serverMode bool - unsafeAPI bool -} - -var defaultDevOptions = &devCfg{ - chainId: "dev", - chainDomain: "gno.land", - maxGas: 10_000_000_000, - webListenerAddr: "127.0.0.1:8888", - nodeRPCListenerAddr: "127.0.0.1:26657", - deployKey: DefaultDeployerAddress.String(), - home: gnoenv.HomeDir(), - root: gnoenv.RootDir(), - - // As we have no reason to configure this yet, set this to random port - // to avoid potential conflict with other app - nodeP2PListenerAddr: "tcp://127.0.0.1:0", - nodeProxyAppListenerAddr: "tcp://127.0.0.1:0", -} - func main() { - cfg := &devCfg{} - stdio := commands.NewDefaultIO() + + localcmd := NewLocalCmd(stdio) // default + 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 'examples' directory and any additional specified paths.`, - }, - cfg, - func(_ context.Context, args []string) error { - return execDev(cfg, args, stdio) - }) - - cmd.Execute(context.Background(), os.Args[1:]) -} - -func (c *devCfg) RegisterFlags(fs *flag.FlagSet) { - fs.StringVar( - &c.home, - "home", - defaultDevOptions.home, - "user's local directory for keys", - ) - - fs.StringVar( - &c.root, - "root", - defaultDevOptions.root, - "gno root directory", - ) - - fs.BoolVar( - &c.noWeb, - "no-web", - defaultDevOptions.noWeb, - "disable gnoweb", - ) - - fs.BoolVar( - &c.webHTML, - "web-html", - defaultDevOptions.webHTML, - "gnoweb: enable unsafe HTML parsing in markdown rendering", - ) - - fs.StringVar( - &c.webListenerAddr, - "web-listener", - defaultDevOptions.webListenerAddr, - "gnoweb: web server listener address", - ) - - fs.StringVar( - &c.webRemoteHelperAddr, - "web-help-remote", - defaultDevOptions.webRemoteHelperAddr, - "gnoweb: web server help page's remote addr (default to )", - ) - - fs.StringVar( - &c.nodeRPCListenerAddr, - "node-rpc-listener", - defaultDevOptions.nodeRPCListenerAddr, - "listening address for GnoLand RPC node", - ) - - fs.Var( - &c.premineAccounts, - "add-account", - "add (or set) a premine account in the form `[=]`, can be used multiple time", - ) - - fs.StringVar( - &c.balancesFile, - "balance-file", - defaultDevOptions.balancesFile, - "load the provided balance file (refer to the documentation for format)", - ) - - fs.StringVar( - &c.txsFile, - "txs-file", - defaultDevOptions.txsFile, - "load the provided transactions file (refer to the documentation for format)", - ) - - fs.StringVar( - &c.genesisFile, - "genesis", - defaultDevOptions.genesisFile, - "load the given genesis file", - ) - - fs.StringVar( - &c.deployKey, - "deploy-key", - defaultDevOptions.deployKey, - "default key name or Bech32 address for deploying packages", - ) + ShortUsage: "gnodev [flags] ", + 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. - fs.BoolVar( - &c.minimal, - "minimal", - defaultDevOptions.minimal, - "do not load packages from the examples directory", - ) - - fs.BoolVar( - &c.serverMode, - "server-mode", - defaultDevOptions.serverMode, - "disable interaction, and adjust logging for server use.", - ) - - fs.BoolVar( - &c.verbose, - "v", - defaultDevOptions.verbose, - "enable verbose output for development", - ) - - fs.StringVar( - &c.chainId, - "chain-id", - defaultDevOptions.chainId, - "set node ChainID", - ) - - fs.StringVar( - &c.chainDomain, - "chain-domain", - defaultDevOptions.chainDomain, - "set node ChainDomain", - ) - - fs.BoolVar( - &c.noWatch, - "no-watch", - defaultDevOptions.noWatch, - "do not watch for file changes", - ) - - fs.BoolVar( - &c.noReplay, - "no-replay", - defaultDevOptions.noReplay, - "do not replay previous transactions upon reload", - ) - - fs.Int64Var( - &c.maxGas, - "max-gas", - defaultDevOptions.maxGas, - "set the maximum gas per block", - ) - - fs.BoolVar( - &c.unsafeAPI, - "unsafe-api", - defaultDevOptions.unsafeAPI, - "enable /reset and /reload endpoints which are not safe to expose publicly", +If no command is provided, gnodev will automatically start in mode. +For more information and flags usage description, use 'gnodev local -h'.`, + }, + nil, + func(ctx context.Context, _ []string) error { + localcmd.Execute(ctx, os.Args[1:]) + return nil + }, ) -} - -func (c *devCfg) validateConfigFlags() error { - if (c.balancesFile != "" || c.txsFile != "") && c.genesisFile != "" { - return ErrConflictingFileArgs - } - - return nil -} - -func execDev(cfg *devCfg, args []string, io commands.IO) (err error) { - ctx, cancel := context.WithCancelCause(context.Background()) - defer cancel(nil) - - if err := cfg.validateConfigFlags(); err != nil { - return fmt.Errorf("validate error: %w", err) - } - // Setup Raw Terminal - rt, restore, err := setupRawTerm(cfg, io) - if err != nil { - return fmt.Errorf("unable to init raw term: %w", err) - } - defer restore() - - // Setup trap signal - osm.TrapSignal(func() { - cancel(nil) - restore() - }) - - logger := setuplogger(cfg, rt) - loggerEvents := logger.WithGroup(EventServerLogName) - emitterServer := emitter.NewServer(loggerEvents) - - // load keybase - book, err := setupAddressBook(logger.WithGroup(AccountsLogName), cfg) - if err != nil { - return fmt.Errorf("unable to load keybase: %w", err) - } - - // Check and Parse packages - pkgpaths, err := resolvePackagesPathFromArgs(cfg, book, args) - if err != nil { - return fmt.Errorf("unable to parse package paths: %w", err) - } - - // generate balances - balances, err := generateBalances(book, cfg) - if err != nil { - return fmt.Errorf("unable to generate balances: %w", err) - } - logger.Debug("balances loaded", "list", balances.List()) - - // Setup Dev Node - // XXX: find a good way to export or display node logs - nodeLogger := logger.WithGroup(NodeLogName) - nodeCfg := setupDevNodeConfig(cfg, logger, emitterServer, balances, pkgpaths) - devNode, err := setupDevNode(ctx, cfg, nodeCfg) - if err != nil { - return err - } - defer devNode.Close() - - nodeLogger.Info("node started", "lisn", devNode.GetRemoteAddress(), "chainID", cfg.chainId) - - // Create server - mux := http.NewServeMux() - server := http.Server{ - Handler: mux, - Addr: cfg.webListenerAddr, - ReadHeaderTimeout: time.Second * 60, - } - defer server.Close() - - // Setup gnoweb - webhandler, err := setupGnoWebServer(logger.WithGroup(WebLogName), cfg, devNode) - if err != nil { - return fmt.Errorf("unable to setup gnoweb server: %w", err) - } - - // Setup unsafe APIs if enabled - if cfg.unsafeAPI { - mux.HandleFunc("/reset", func(res http.ResponseWriter, req *http.Request) { - if err := devNode.Reset(req.Context()); err != nil { - logger.Error("failed to reset", slog.Any("err", err)) - res.WriteHeader(http.StatusInternalServerError) - } - }) - - mux.HandleFunc("/reload", func(res http.ResponseWriter, req *http.Request) { - if err := devNode.Reload(req.Context()); err != nil { - logger.Error("failed to reload", slog.Any("err", err)) - res.WriteHeader(http.StatusInternalServerError) - } - }) - } - - // Setup HotReload if needed - if !cfg.noWatch { - evtstarget := fmt.Sprintf("%s/_events", server.Addr) - mux.Handle("/_events", emitterServer) - mux.Handle("/", emitter.NewMiddleware(evtstarget, webhandler)) - } else { - mux.Handle("/", webhandler) - } - - // Serve gnoweb - if !cfg.noWeb { - go func() { - err := server.ListenAndServe() - cancel(err) - }() - - logger.WithGroup(WebLogName). - Info("gnoweb started", - "lisn", fmt.Sprintf("http://%s", server.Addr)) - } - - watcher, err := watcher.NewPackageWatcher(loggerEvents, emitterServer) - if err != nil { - return fmt.Errorf("unable to setup packages watcher: %w", err) - } - defer watcher.Stop() - - // Add node pkgs to watcher - watcher.AddPackages(devNode.ListPkgs()...) - - if !cfg.serverMode { - logger.WithGroup("--- READY").Info("for commands and help, press `h`") - } - - // Run the main event loop - return runEventLoop(ctx, logger, book, rt, devNode, watcher) -} - -var helper string = `For more in-depth documentation, visit the GNO Tooling CLI documentation: -https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev - -P Previous TX - Go to the previous tx -N Next TX - Go to the next tx -E Export - Export the current state as genesis doc -A Accounts - Display known accounts and balances -H Help - Display this message -R Reload - Reload all packages to take change into account. -Ctrl+S Save State - Save the current state -Ctrl+R Reset - Reset application to it's initial/save state. -Ctrl+C Exit - Exit the application -` - -func runEventLoop( - ctx context.Context, - logger *slog.Logger, - bk *address.Book, - rt *rawterm.RawTerm, - dnode *gnodev.Node, - watch *watcher.PackageWatcher, -) error { - // XXX: move this in above, but we need to have a proper struct first - // XXX: make this configurable - var exported uint - path, err := os.MkdirTemp("", "gnodev-export") - if err != nil { - return fmt.Errorf("unable to create `export` directory: %w", err) - } + cmd.AddSubCommands(localcmd) + cmd.AddSubCommands(NewStagingCmd(stdio)) - defer func() { - if exported == 0 { - _ = os.RemoveAll(path) - } - }() - - keyPressCh := listenForKeyPress(logger.WithGroup(KeyPressLogName), rt) - for { - var err error - - select { - case <-ctx.Done(): - return context.Cause(ctx) - case pkgs, ok := <-watch.PackagesUpdate: - if !ok { - return nil - } - - // fmt.Fprintln(nodeOut, "Loading package updates...") - if err = dnode.UpdatePackages(pkgs.PackagesPath()...); err != nil { - return fmt.Errorf("unable to update packages: %w", err) - } - - logger.WithGroup(NodeLogName).Info("reloading...") - if err = dnode.Reload(ctx); err != nil { - logger.WithGroup(NodeLogName). - Error("unable to reload node", "err", err) - } - - case key, ok := <-keyPressCh: - if !ok { - return nil - } - - logger.WithGroup(KeyPressLogName).Debug( - fmt.Sprintf("<%s>", key.String()), - ) - - switch key.Upper() { - case rawterm.KeyH: // Helper - logger.Info("Gno Dev Helper", "helper", helper) - - case rawterm.KeyA: // Accounts - logAccounts(logger.WithGroup(AccountsLogName), bk, dnode) - - case rawterm.KeyR: // Reload - logger.WithGroup(NodeLogName).Info("reloading...") - if err = dnode.ReloadAll(ctx); err != nil { - logger.WithGroup(NodeLogName). - Error("unable to reload node", "err", err) - } - - case rawterm.KeyCtrlR: // Reset - logger.WithGroup(NodeLogName).Info("reseting node state...") - if err = dnode.Reset(ctx); err != nil { - logger.WithGroup(NodeLogName). - Error("unable to reset node state", "err", err) - } - - case rawterm.KeyCtrlS: // Save - logger.WithGroup(NodeLogName).Info("saving state...") - if err := dnode.SaveCurrentState(ctx); err != nil { - logger.WithGroup(NodeLogName). - Error("unable to save node state", "err", err) - } - - case rawterm.KeyE: - logger.WithGroup(NodeLogName).Info("exporting state...") - doc, err := dnode.ExportStateAsGenesis(ctx) - if err != nil { - logger.WithGroup(NodeLogName). - Error("unable to export node state", "err", err) - continue - } - - docfile := filepath.Join(path, fmt.Sprintf("export_%d.jsonl", exported)) - if err := doc.SaveAs(docfile); err != nil { - logger.WithGroup(NodeLogName). - Error("unable to save genesis", "err", err) - } - exported++ - - logger.WithGroup(NodeLogName).Info("node state exported", "file", docfile) - - case rawterm.KeyN: // Next tx - logger.Info("moving forward...") - if err := dnode.MoveToNextTX(ctx); err != nil { - logger.WithGroup(NodeLogName). - Error("unable to move forward", "err", err) - } - - case rawterm.KeyP: // Next tx - logger.Info("moving backward...") - if err := dnode.MoveToPreviousTX(ctx); err != nil { - logger.WithGroup(NodeLogName). - Error("unable to move backward", "err", err) - } - - case rawterm.KeyCtrlC: // Exit - return nil - - default: - } - - // Reset listen for the next keypress - keyPressCh = listenForKeyPress(logger.WithGroup(KeyPressLogName), rt) - } - } -} - -func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.KeyPress { - cc := make(chan rawterm.KeyPress, 1) - go func() { - defer close(cc) - key, err := rt.ReadKeyPress() - if err != nil { - logger.Error("unable to read keypress", "err", err) + // XXX: This part is a bit hacky; it mostly configures the command to + // use the local command as default, but still falls back on gnodev root + // help if asked. + var buff bytes.Buffer + cmd.SetOutput(&buff) + if err := cmd.Parse(os.Args[1:]); err != nil { + if !errors.Is(err, flag.ErrHelp) { + localcmd.Execute(context.Background(), os.Args[1:]) return } - cc <- key - }() - - return cc -} - -func resolvePackagesPathFromArgs(cfg *devCfg, bk *address.Book, args []string) ([]gnodev.PackagePath, error) { - paths := make([]gnodev.PackagePath, 0, len(args)) - - if cfg.deployKey == "" { - return nil, fmt.Errorf("default deploy key cannot be empty") - } - - defaultKey, _, ok := bk.GetFromNameOrAddress(cfg.deployKey) - if !ok { - return nil, fmt.Errorf("unable to get deploy key %q", cfg.deployKey) - } - - for _, arg := range args { - path, err := gnodev.ResolvePackagePathQuery(bk, arg) - if err != nil { - return nil, fmt.Errorf("invalid package path/query %q: %w", arg, err) - } - - // Assign a default creator if user haven't specified it. - if path.Creator.IsZero() { - path.Creator = defaultKey + if buff.Len() > 0 { + fmt.Fprint(stdio.Err(), buff.String()) } - paths = append(paths, path) + return } - // Add examples folder if minimal is set to false - if !cfg.minimal { - paths = append(paths, gnodev.PackagePath{ - Path: filepath.Join(cfg.root, "examples"), - Creator: defaultKey, - Deposit: nil, - }) + if err := cmd.Run(context.Background()); err != nil { + stdio.ErrPrintfln(err.Error()) } - - return paths, nil } diff --git a/contribs/gnodev/cmd/gnodev/path_manager.go b/contribs/gnodev/cmd/gnodev/path_manager.go new file mode 100644 index 00000000000..705e90fe2c4 --- /dev/null +++ b/contribs/gnodev/cmd/gnodev/path_manager.go @@ -0,0 +1,45 @@ +package main + +import ( + "sync" +) + +// pathManager manages a set of unique paths. +type pathManager struct { + paths map[string]struct{} + mu sync.RWMutex +} + +func newPathManager() *pathManager { + return &pathManager{ + paths: make(map[string]struct{}), + } +} + +// Save add one path to the PathManager. If a path already exists, it is not added again. +func (p *pathManager) Save(path string) (exist bool) { + p.mu.Lock() + defer p.mu.Unlock() + if _, exist = p.paths[path]; !exist { + p.paths[path] = struct{}{} + } + return exist +} + +func (p *pathManager) List() []string { + p.mu.RLock() + defer p.mu.RUnlock() + + paths := make([]string, 0, len(p.paths)) + for path := range p.paths { + paths = append(paths, path) + } + + return paths +} + +func (p *pathManager) Reset() { + p.mu.Lock() + defer p.mu.Unlock() + p.paths = make(map[string]struct{}) +} diff --git a/contribs/gnodev/cmd/gnodev/setup_address_book.go b/contribs/gnodev/cmd/gnodev/setup_address_book.go index a1a1c8f58ac..5d10b748a22 100644 --- a/contribs/gnodev/cmd/gnodev/setup_address_book.go +++ b/contribs/gnodev/cmd/gnodev/setup_address_book.go @@ -9,7 +9,7 @@ import ( osm "github.com/gnolang/gno/tm2/pkg/os" ) -func setupAddressBook(logger *slog.Logger, cfg *devCfg) (*address.Book, error) { +func setupAddressBook(logger *slog.Logger, cfg *AppConfig) (*address.Book, error) { book := address.NewBook() // Check for home folder @@ -40,24 +40,24 @@ func setupAddressBook(logger *slog.Logger, cfg *devCfg) (*address.Book, error) { } // Ensure that we have a default address - names, ok := book.GetByAddress(DefaultDeployerAddress) + names, ok := book.GetByAddress(defaultDeployerAddress) if ok { // Account already exist in the keybase if len(names) > 0 && names[0] != "" { - logger.Info("default address imported", "name", names[0], "addr", DefaultDeployerAddress.String()) + logger.Info("default address imported", "name", names[0], "addr", defaultDeployerAddress.String()) } else { - logger.Info("default address imported", "addr", DefaultDeployerAddress.String()) + logger.Info("default address imported", "addr", defaultDeployerAddress.String()) } return book, nil } // If the key isn't found, create a default one - creatorName := fmt.Sprintf("_default#%.6s", DefaultDeployerAddress.String()) - book.Add(DefaultDeployerAddress, creatorName) + creatorName := fmt.Sprintf("_default#%.6s", defaultDeployerAddress.String()) + book.Add(defaultDeployerAddress, creatorName) logger.Warn("default address created", "name", creatorName, - "addr", DefaultDeployerAddress.String(), + "addr", defaultDeployerAddress.String(), "mnemonic", DefaultDeployerSeed, ) diff --git a/contribs/gnodev/cmd/gnodev/setup_loader.go b/contribs/gnodev/cmd/gnodev/setup_loader.go new file mode 100644 index 00000000000..69342657352 --- /dev/null +++ b/contribs/gnodev/cmd/gnodev/setup_loader.go @@ -0,0 +1,108 @@ +package main + +import ( + "fmt" + "log/slog" + gopath "path" + "path/filepath" + "regexp" + "strings" + + "github.com/gnolang/gno/contribs/gnodev/pkg/packages" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" +) + +type varResolver []packages.Resolver + +func (va varResolver) String() string { + resolvers := packages.ChainedResolver(va) + return resolvers.Name() +} + +func (va *varResolver) Set(value string) error { + name, location, found := strings.Cut(value, "=") + if !found { + return fmt.Errorf("invalid resolver format %q, should be `=`", value) + } + + var res packages.Resolver + switch name { + case "remote": + rpc, err := client.NewHTTPClient(location) + if err != nil { + return fmt.Errorf("invalid resolver remote: %q", location) + } + + res = packages.NewRemoteResolver(location, rpc) + case "root": // process everything from a root directory + res = packages.NewRootResolver(location) + case "local": // process a single directory + path, ok := guessPathGnoMod(location) + if !ok { + return fmt.Errorf("unable to read module path from gno.mod in %q", location) + } + + res = packages.NewLocalResolver(path, location) + default: + return fmt.Errorf("invalid resolver name: %q", name) + } + + *va = append(*va, res) + return nil +} + +func setupPackagesResolver(logger *slog.Logger, cfg *AppConfig, dirs ...string) (packages.Resolver, []string) { + // Add root resolvers + localResolvers := make([]packages.Resolver, len(dirs)) + + var paths []string + for i, dir := range dirs { + path := guessPath(cfg, dir) + resolver := packages.NewLocalResolver(path, dir) + + if resolver.IsValid() { + logger.Info("guessing directory path", "path", path, "dir", dir) + paths = append(paths, path) // append local path + } else { + logger.Warn("no gno package found", "dir", dir) + } + + localResolvers[i] = resolver + } + + resolver := packages.ChainResolvers( + packages.ChainResolvers(localResolvers...), // Resolve local directories + packages.ChainResolvers(cfg.resolvers...), // Use user's custom resolvers + ) + + // Enrich resolver with middleware + return packages.MiddlewareResolver(resolver, + packages.CacheMiddleware(func(pkg *packages.Package) bool { + return pkg.Kind == packages.PackageKindRemote // Only cache remote package + }), + packages.FilterStdlibs, // Filter stdlib package from resolving + packages.PackageCheckerMiddleware(logger), // Pre-check syntax to avoid bothering the node reloading on invalid files + packages.LogMiddleware(logger), // Log request + ), paths +} + +func guessPathGnoMod(dir string) (path string, ok bool) { + modfile, err := gnomod.ParseAt(dir) + if err == nil { + return modfile.Module.Mod.Path, true + } + + return "", false +} + +var reInvalidChar = regexp.MustCompile(`[^\w_-]`) + +func guessPath(cfg *AppConfig, dir string) (path string) { + if path, ok := guessPathGnoMod(dir); ok { + return path + } + + rname := reInvalidChar.ReplaceAllString(filepath.Base(dir), "-") + return gopath.Join(cfg.chainDomain, "/r/dev/", rname) +} diff --git a/contribs/gnodev/cmd/gnodev/setup_node.go b/contribs/gnodev/cmd/gnodev/setup_node.go index eaeb89b7e95..761bdef0aef 100644 --- a/contribs/gnodev/cmd/gnodev/setup_node.go +++ b/contribs/gnodev/cmd/gnodev/setup_node.go @@ -9,28 +9,25 @@ import ( gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev" "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" + "github.com/gnolang/gno/contribs/gnodev/pkg/packages" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" ) // setupDevNode initializes and returns a new DevNode. -func setupDevNode( - ctx context.Context, - devCfg *devCfg, - nodeConfig *gnodev.NodeConfig, -) (*gnodev.Node, error) { +func setupDevNode(ctx context.Context, cfg *AppConfig, nodeConfig *gnodev.NodeConfig, paths ...string) (*gnodev.Node, error) { logger := nodeConfig.Logger - if devCfg.txsFile != "" { // Load txs files + if cfg.txsFile != "" { // Load txs files var err error - nodeConfig.InitialTxs, err = gnoland.ReadGenesisTxs(ctx, devCfg.txsFile) + nodeConfig.InitialTxs, err = gnoland.ReadGenesisTxs(ctx, cfg.txsFile) if err != nil { return nil, fmt.Errorf("unable to load transactions: %w", err) } - } else if devCfg.genesisFile != "" { // Load genesis file - state, err := extractAppStateFromGenesisFile(devCfg.genesisFile) + } else if cfg.genesisFile != "" { // Load genesis file + state, err := extractAppStateFromGenesisFile(cfg.genesisFile) if err != nil { - return nil, fmt.Errorf("unable to load genesis file %q: %w", devCfg.genesisFile, err) + return nil, fmt.Errorf("unable to load genesis file %q: %w", cfg.genesisFile, err) } // Override balances and txs @@ -43,34 +40,40 @@ func setupDevNode( nodeConfig.InitialTxs[index] = nodeTx } - logger.Info("genesis file loaded", "path", devCfg.genesisFile, "txs", len(stateTxs)) + logger.Info("genesis file loaded", "path", cfg.genesisFile, "txs", len(stateTxs)) } - return gnodev.NewDevNode(ctx, nodeConfig) + if len(paths) > 0 { + logger.Info("packages", "paths", paths) + } else { + logger.Debug("no path(s) provided") + } + + return gnodev.NewDevNode(ctx, nodeConfig, paths...) } // setupDevNodeConfig creates and returns a new dev.NodeConfig. func setupDevNodeConfig( - cfg *devCfg, + cfg *AppConfig, logger *slog.Logger, emitter emitter.Emitter, balances gnoland.Balances, - pkgspath []gnodev.PackagePath, + loader packages.Loader, ) *gnodev.NodeConfig { config := gnodev.DefaultNodeConfig(cfg.root, cfg.chainDomain) + config.Loader = loader config.Logger = logger config.Emitter = emitter config.BalancesList = balances.List() - config.PackagesPathList = pkgspath - config.TMConfig.RPC.ListenAddress = resolveUnixOrTCPAddr(cfg.nodeRPCListenerAddr) + config.TMConfig.RPC.ListenAddress = cfg.nodeRPCListenerAddr config.NoReplay = cfg.noReplay config.MaxGasPerBlock = cfg.maxGas config.ChainID = cfg.chainId // other listeners - config.TMConfig.P2P.ListenAddress = defaultDevOptions.nodeP2PListenerAddr - config.TMConfig.ProxyApp = defaultDevOptions.nodeProxyAppListenerAddr + config.TMConfig.P2P.ListenAddress = defaultLocalAppConfig.nodeP2PListenerAddr + config.TMConfig.ProxyApp = defaultLocalAppConfig.nodeProxyAppListenerAddr return config } @@ -89,21 +92,20 @@ func extractAppStateFromGenesisFile(path string) (*gnoland.GnoGenesisState, erro return &state, nil } -func resolveUnixOrTCPAddr(in string) (out string) { +func resolveUnixOrTCPAddr(in string) (addr net.Addr) { var err error - var addr net.Addr if strings.HasPrefix(in, "unix://") { in = strings.TrimPrefix(in, "unix://") - if addr, err := net.ResolveUnixAddr("unix", in); err == nil { - return fmt.Sprintf("%s://%s", addr.Network(), addr.String()) + if addr, err = net.ResolveUnixAddr("unix", in); err == nil { + return addr } err = fmt.Errorf("unable to resolve unix address `unix://%s`: %w", in, err) } else { // don't bother to checking prefix in = strings.TrimPrefix(in, "tcp://") if addr, err = net.ResolveTCPAddr("tcp", in); err == nil { - return fmt.Sprintf("%s://%s", addr.Network(), addr.String()) + return addr } err = fmt.Errorf("unable to resolve tcp address `tcp://%s`: %w", in, err) diff --git a/contribs/gnodev/cmd/gnodev/setup_term.go b/contribs/gnodev/cmd/gnodev/setup_term.go index 1f8f3046969..fb8b5593abf 100644 --- a/contribs/gnodev/cmd/gnodev/setup_term.go +++ b/contribs/gnodev/cmd/gnodev/setup_term.go @@ -7,10 +7,10 @@ import ( var noopRestore = func() error { return nil } -func setupRawTerm(cfg *devCfg, io commands.IO) (*rawterm.RawTerm, func() error, error) { +func setupRawTerm(cfg *AppConfig, io commands.IO) (*rawterm.RawTerm, func() error, error) { rt := rawterm.NewRawTerm() restore := noopRestore - if !cfg.serverMode { + if cfg.interactive { var err error restore, err = rt.Init() if err != nil { diff --git a/contribs/gnodev/cmd/gnodev/setup_web.go b/contribs/gnodev/cmd/gnodev/setup_web.go index e509768d2a1..09df8ce009c 100644 --- a/contribs/gnodev/cmd/gnodev/setup_web.go +++ b/contribs/gnodev/cmd/gnodev/setup_web.go @@ -5,24 +5,23 @@ import ( "log/slog" "net/http" - gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev" "github.com/gnolang/gno/gno.land/pkg/gnoweb" ) // setupGnowebServer initializes and starts the Gnoweb server. -func setupGnoWebServer(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) (http.Handler, error) { +func setupGnoWebServer(logger *slog.Logger, cfg *AppConfig, remoteAddr string) (http.Handler, error) { if cfg.noWeb { return http.HandlerFunc(http.NotFound), nil } - remote := dnode.GetRemoteAddress() - appcfg := gnoweb.NewDefaultAppConfig() appcfg.UnsafeHTML = cfg.webHTML - appcfg.NodeRemote = remote + appcfg.NodeRemote = remoteAddr appcfg.ChainID = cfg.chainId if cfg.webRemoteHelperAddr != "" { appcfg.RemoteHelp = cfg.webRemoteHelperAddr + } else { + appcfg.RemoteHelp = remoteAddr } router, err := gnoweb.NewRouter(logger, appcfg) @@ -30,5 +29,11 @@ func setupGnoWebServer(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) (ht return nil, fmt.Errorf("unable to create router app: %w", err) } + logger.Debug("gnoweb router created", + "remote", appcfg.NodeRemote, + "helper_remote", appcfg.RemoteHelp, + "html", appcfg.UnsafeHTML, + "chain_id", cfg.chainId, + ) return router, nil } diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 92d8494fa40..f520e2fbb79 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -1,8 +1,6 @@ module github.com/gnolang/gno/contribs/gnodev -go 1.22 - -toolchain go1.22.4 +go 1.23.6 replace github.com/gnolang/gno => ../.. @@ -18,18 +16,19 @@ require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 github.com/gorilla/websocket v1.5.3 github.com/lrstanley/bubblezone v0.0.0-20240624011428-67235275f80c + github.com/mattn/go-isatty v0.0.20 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 github.com/sahilm/fuzzy v0.1.1 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 - golang.org/x/sync v0.8.0 - golang.org/x/term v0.23.0 + golang.org/x/sync v0.10.0 + golang.org/x/term v0.28.0 ) require ( dario.cat/mergo v1.0.1 // indirect - github.com/alecthomas/chroma/v2 v2.14.0 // indirect + github.com/alecthomas/chroma/v2 v2.15.0 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect @@ -46,11 +45,11 @@ require ( github.com/charmbracelet/x/termios v0.1.0 // indirect github.com/charmbracelet/x/windows v0.1.2 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect github.com/creack/pty v1.1.21 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/dlclark/regexp2 v1.11.0 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -59,10 +58,9 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/microcosm-cc/bluemonday v1.0.25 // indirect @@ -74,38 +72,40 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/yuin/goldmark v1.7.2 // indirect + github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect - golang.org/x/tools v0.24.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index 3f22e4f2f00..f4bf32aafd5 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -1,11 +1,11 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= -github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= -github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc= +github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= @@ -78,8 +78,8 @@ github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelh github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -95,8 +95,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -137,8 +137,8 @@ github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -205,8 +205,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -222,16 +222,18 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.2 h1:NjGd7lO7zrUn/A7eKwn5PEOt4ONYGqpxSEeZuduvgxc= -github.com/yuin/goldmark v1.7.2/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= @@ -242,22 +244,24 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -269,22 +273,22 @@ go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -296,35 +300,35 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnodev/internal/mock/server_emitter.go b/contribs/gnodev/internal/mock/emitter/server_emitter.go similarity index 100% rename from contribs/gnodev/internal/mock/server_emitter.go rename to contribs/gnodev/internal/mock/emitter/server_emitter.go diff --git a/contribs/gnodev/pkg/browser/model.go b/contribs/gnodev/pkg/browser/model.go index bdd7eac9c82..148497e77db 100644 --- a/contribs/gnodev/pkg/browser/model.go +++ b/contribs/gnodev/pkg/browser/model.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" "log/slog" - "path/filepath" + gopath "path" "regexp" "strings" @@ -576,5 +576,5 @@ func (m *model) getCurrentPath() string { return m.urlPrefix } - return filepath.Join(m.urlPrefix, path) + return gopath.Join(m.urlPrefix, path) } diff --git a/contribs/gnodev/pkg/browser/utils.go b/contribs/gnodev/pkg/browser/utils.go index b322bea552a..94f0bd6344c 100644 --- a/contribs/gnodev/pkg/browser/utils.go +++ b/contribs/gnodev/pkg/browser/utils.go @@ -1,7 +1,7 @@ package browser import ( - "path/filepath" + gopath "path" "strings" "github.com/gnolang/gno/gno.land/pkg/gnoweb" @@ -27,7 +27,7 @@ func cleanupRealmPath(prefix, realm string) string { // trim any slash path = strings.TrimPrefix(path, "/") // clean up path - path = filepath.Clean(path) + path = gopath.Clean(path) return path } diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 12a88490515..0b25234f350 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -6,6 +6,7 @@ import ( "log/slog" "os" "path/filepath" + "slices" "strings" "sync" "time" @@ -13,11 +14,14 @@ import ( "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" "github.com/gnolang/gno/contribs/gnodev/pkg/events" + "github.com/gnolang/gno/contribs/gnodev/pkg/packages" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/integration" - "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/tm2/pkg/amino" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" @@ -32,18 +36,52 @@ import ( ) type NodeConfig struct { - Logger *slog.Logger - DefaultDeployer crypto.Address - BalancesList []gnoland.Balance - PackagesPathList []PackagePath - Emitter emitter.Emitter - InitialTxs []gnoland.TxWithMetadata - TMConfig *tmcfg.Config + // Logger is used for logging node activities. It can be set to a custom logger or a noop logger for + // silent operation. + Logger *slog.Logger + + // Loader is responsible for loading packages. It abstracts the mechanism for retrieving and managing + // package data. + Loader packages.Loader + + // DefaultCreator specifies the default address used for creating packages and transactions. + DefaultCreator crypto.Address + + // DefaultDeposit is the default amount of coins deposited when creating a package. + DefaultDeposit std.Coins + + // BalancesList defines the initial balance of accounts in the genesis state. + BalancesList []gnoland.Balance + + // PackagesModifier allows modifications to be applied to packages during initialization. + PackagesModifier []QueryPath + + // Emitter is used to emit events for various node operations. It can be set to a noop emitter if no + // event emission is required. + Emitter emitter.Emitter + + // InitialTxs contains the transactions that are included in the genesis state. + InitialTxs []gnoland.TxWithMetadata + + // TMConfig holds the Tendermint configuration settings. + TMConfig *tmcfg.Config + + // SkipFailingGenesisTxs indicates whether to skip failing transactions during the genesis + // initialization. SkipFailingGenesisTxs bool - NoReplay bool - MaxGasPerBlock int64 - ChainID string - ChainDomain string + + // NoReplay, if set to true, prevents replaying of transactions from the block store during node + // initialization. + NoReplay bool + + // MaxGasPerBlock sets the maximum amount of gas that can be used in a single block. + MaxGasPerBlock int64 + + // ChainID is the unique identifier for the blockchain. + ChainID string + + // ChainDomain specifies the domain name associated with the blockchain network. + ChainDomain string } func DefaultNodeConfig(rootdir, domain string) *NodeConfig { @@ -60,10 +98,15 @@ func DefaultNodeConfig(rootdir, domain string) *NodeConfig { }, } + exampleFolder := filepath.Join(gnoenv.RootDir(), "example") // XXX: we should avoid having to hardcoding this here + defaultLoader := packages.NewLoader(packages.NewRootResolver(exampleFolder)) + return &NodeConfig{ Logger: log.NewNoopLogger(), Emitter: &emitter.NoopServer{}, - DefaultDeployer: defaultDeployer, + Loader: defaultLoader, + DefaultCreator: defaultDeployer, + DefaultDeposit: nil, BalancesList: balances, ChainID: tmc.ChainID(), ChainDomain: domain, @@ -78,11 +121,14 @@ type Node struct { *node.Node muNode sync.RWMutex - config *NodeConfig - emitter emitter.Emitter - client client.Client - logger *slog.Logger - pkgs PackagesMap // path -> pkg + config *NodeConfig + emitter emitter.Emitter + client client.Client + logger *slog.Logger + loader packages.Loader + pkgs []packages.Package + pkgsModifier map[string]QueryPath // path -> QueryPath + paths []string // keep track of number of loaded package to be able to skip them on restore loadedPackages int @@ -97,36 +143,30 @@ type Node struct { var DefaultFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) -func NewDevNode(ctx context.Context, cfg *NodeConfig) (*Node, error) { - mpkgs, err := NewPackagesMap(cfg.PackagesPathList) - if err != nil { - return nil, fmt.Errorf("unable map pkgs list: %w", err) - } - +func NewDevNode(ctx context.Context, cfg *NodeConfig, pkgpaths ...string) (*Node, error) { startTime := time.Now() - pkgsTxs, err := mpkgs.Load(DefaultFee, startTime) - if err != nil { - return nil, fmt.Errorf("unable to load genesis packages: %w", err) + + pkgsModifier := make(map[string]QueryPath, len(cfg.PackagesModifier)) + for _, qpath := range cfg.PackagesModifier { + pkgsModifier[qpath.Path] = qpath } - cfg.Logger.Info("pkgs loaded", "path", cfg.PackagesPathList) devnode := &Node{ + loader: cfg.Loader, config: cfg, client: client.NewLocal(), emitter: cfg.Emitter, - pkgs: mpkgs, logger: cfg.Logger, - loadedPackages: len(pkgsTxs), startTime: startTime, state: cfg.InitialTxs, initialState: cfg.InitialTxs, currentStateIndex: len(cfg.InitialTxs), + paths: pkgpaths, + pkgsModifier: pkgsModifier, } - genesis := gnoland.DefaultGenState() - genesis.Balances = cfg.BalancesList - genesis.Txs = append(pkgsTxs, cfg.InitialTxs...) - if err := devnode.rebuildNode(ctx, genesis); err != nil { + // XXX: MOVE THIS, passing context here can be confusing + if err := devnode.Reset(ctx); err != nil { return nil, fmt.Errorf("unable to initialize the node: %w", err) } @@ -140,11 +180,11 @@ func (n *Node) Close() error { return n.Node.Stop() } -func (n *Node) ListPkgs() []gnomod.Pkg { +func (n *Node) ListPkgs() []packages.Package { n.muNode.RLock() defer n.muNode.RUnlock() - return n.pkgs.toList() + return n.pkgs } func (n *Node) Client() client.Client { @@ -158,8 +198,38 @@ func (n *Node) GetRemoteAddress() string { return n.Node.Config().RPC.ListenAddress } +// AddPackagePaths to load +func (n *Node) AddPackagePaths(paths ...string) { + n.muNode.Lock() + defer n.muNode.Unlock() + + n.paths = append(n.paths, paths...) +} + +func (n *Node) SetPackagePaths(paths ...string) { + n.muNode.Lock() + defer n.muNode.Unlock() + + n.paths = paths +} + +// HasPackageLoaded returns true if the specified package has already been loaded. +// NOTE: This only checks if the package was loaded at the genesis level. +func (n *Node) HasPackageLoaded(path string) bool { + n.muNode.RLock() + defer n.muNode.RUnlock() + + for _, pkg := range n.pkgs { + if pkg.MemPackage.Path == path { + return true + } + } + + return false +} + // GetBlockTransactions returns the transactions contained -// within the specified block, if any +// within the specified block, if any. func (n *Node) GetBlockTransactions(blockNum uint64) ([]gnoland.TxWithMetadata, error) { n.muNode.RLock() defer n.muNode.RUnlock() @@ -168,36 +238,63 @@ func (n *Node) GetBlockTransactions(blockNum uint64) ([]gnoland.TxWithMetadata, } // GetBlockTransactions returns the transactions contained -// within the specified block, if any +// within the specified block, if any. func (n *Node) getBlockTransactions(blockNum uint64) ([]gnoland.TxWithMetadata, error) { int64BlockNum := int64(blockNum) b, err := n.client.Block(&int64BlockNum) if err != nil { - return []gnoland.TxWithMetadata{}, fmt.Errorf("unable to load block at height %d: %w", blockNum, err) // nothing to see here + return nil, fmt.Errorf("unable to load block at height %d: %w", blockNum, err) + } + txs := b.Block.Data.Txs + + bres, err := n.client.BlockResults(&int64BlockNum) + if err != nil { + return nil, fmt.Errorf("unable to load block at height %d: %w", blockNum, err) + } + deliverTxs := bres.Results.DeliverTxs + + // Sanity check + if len(txs) != len(deliverTxs) { + panic(fmt.Errorf("invalid block txs len (%d) vs block result txs len (%d)", + len(txs), len(deliverTxs), + )) } - txs := make([]gnoland.TxWithMetadata, len(b.Block.Data.Txs)) - for i, encodedTx := range b.Block.Data.Txs { - // fallback on std tx + txResults := make([]*abci.ResponseDeliverTx, len(deliverTxs)) + for i, tx := range deliverTxs { + txResults[i] = &tx + } + + // XXX: Consider replacing a failed transaction with an empty transaction + // to preserve the transaction height ? + // Note that this would also require committing instead of using the + // genesis block. + + metaTxs := make([]gnoland.TxWithMetadata, 0, len(txs)) + for i, encodedTx := range txs { + if deliverTx := deliverTxs[i]; !deliverTx.IsOK() { + continue // skip failed tx + } + var tx std.Tx if unmarshalErr := amino.Unmarshal(encodedTx, &tx); unmarshalErr != nil { return nil, fmt.Errorf("unable to unmarshal tx: %w", unmarshalErr) } - txs[i] = gnoland.TxWithMetadata{ + metaTxs = append(metaTxs, gnoland.TxWithMetadata{ Tx: tx, Metadata: &gnoland.GnoTxMetadata{ Timestamp: b.BlockMeta.Header.Time.Unix(), }, - } + }) } - return txs, nil + return slices.Clip(metaTxs), nil } // GetBlockTransactions returns the transactions contained -// within the specified block, if any -// GetLatestBlockNumber returns the latest block height from the chain +// within the specified block, if any. +// GetLatestBlockNumber returns the latest block height from the chain. func (n *Node) GetLatestBlockNumber() (uint64, error) { n.muNode.RLock() defer n.muNode.RUnlock() @@ -209,82 +306,25 @@ func (n *Node) getLatestBlockNumber() uint64 { return uint64(n.Node.BlockStore().Height()) } -// UpdatePackages updates the currently known packages. It will be taken into -// consideration in the next reload of the node. -func (n *Node) UpdatePackages(paths ...string) error { - n.muNode.Lock() - defer n.muNode.Unlock() - - return n.updatePackages(paths...) -} - -func (n *Node) updatePackages(paths ...string) error { - var pkgsUpdated int - for _, path := range paths { - abspath, err := filepath.Abs(path) - if err != nil { - return fmt.Errorf("unable to resolve abs path of %q: %w", path, err) - } - - // Check if we already know the path (or its parent) and set - // associated deployer and deposit - deployer := n.config.DefaultDeployer - var deposit std.Coins - for _, ppath := range n.config.PackagesPathList { - if !strings.HasPrefix(abspath, ppath.Path) { - continue - } - - deployer = ppath.Creator - deposit = ppath.Deposit - } - - // List all packages from target path - pkgslist, err := gnomod.ListPkgs(abspath) - 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 { - n.pkgs[pkg.Dir] = Package{ - Pkg: pkg, - Creator: deployer, - Deposit: deposit, - } - - n.logger.Debug("pkgs update", "name", pkg.Name, "path", pkg.Dir) - } - - pkgsUpdated += len(pkgslist) - } - - n.logger.Info(fmt.Sprintf("updated %d packages", pkgsUpdated)) - return nil -} - // Reset stops the node, if running, and reloads it with a new genesis state, // effectively ignoring the current state. func (n *Node) Reset(ctx context.Context) error { n.muNode.Lock() defer n.muNode.Unlock() - // Stop the node if it's currently running. - if err := n.stopIfRunning(); err != nil { - return fmt.Errorf("unable to stop the node: %w", err) - } - // Reset starting time startTime := time.Now() // Generate a new genesis state based on the current packages - pkgsTxs, err := n.pkgs.Load(DefaultFee, startTime) + pkgs, err := n.loader.Load(n.paths...) if err != nil { return fmt.Errorf("unable to load pkgs: %w", err) } // Append initialTxs + pkgsTxs := n.generateTxs(DefaultFee, pkgs) txs := append(pkgsTxs, n.initialState...) + genesis := gnoland.DefaultGenState() genesis.Balances = n.config.BalancesList genesis.Txs = txs @@ -295,6 +335,7 @@ func (n *Node) Reset(ctx context.Context) error { return fmt.Errorf("unable to initialize a new node: %w", err) } + n.pkgs = pkgs n.loadedPackages = len(pkgsTxs) n.currentStateIndex = len(n.initialState) n.startTime = startTime @@ -308,16 +349,6 @@ func (n *Node) ReloadAll(ctx context.Context) error { n.muNode.Lock() defer n.muNode.Unlock() - pkgs := n.pkgs.toList() - paths := make([]string, len(pkgs)) - for i, pkg := range pkgs { - paths[i] = pkg.Dir - } - - if err := n.updatePackages(paths...); err != nil { - return fmt.Errorf("unable to reload packages: %w", err) - } - return n.rebuildNodeFromState(ctx) } @@ -386,10 +417,51 @@ func (n *Node) getBlockStoreState(ctx context.Context) ([]gnoland.TxWithMetadata state = append(state, txs...) } - // override current state return state, nil } +func (n *Node) generateTxs(fee std.Fee, pkgs []packages.Package) []gnoland.TxWithMetadata { + metatxs := make([]gnoland.TxWithMetadata, 0, len(pkgs)) + for _, pkg := range pkgs { + msg := vm.MsgAddPackage{ + Creator: n.config.DefaultCreator, + Deposit: n.config.DefaultDeposit, + Package: &pkg.MemPackage, + } + + if m, ok := n.pkgsModifier[pkg.Path]; ok { + if !m.Creator.IsZero() { + msg.Creator = m.Creator + } + + if m.Deposit != nil { + msg.Deposit = m.Deposit + } + + n.logger.Debug("applying pkgs modifier", + "path", pkg.Path, + "creator", msg.Creator, + "deposit", msg.Deposit, + ) + } + + // Create transaction + tx := std.Tx{Fee: fee, Msgs: []std.Msg{msg}} + tx.Signatures = make([]std.Signature, len(tx.GetSigners())) + + // Wrap it with metadata + metatx := gnoland.TxWithMetadata{ + Tx: tx, + Metadata: &gnoland.GnoTxMetadata{ + Timestamp: n.startTime.Unix(), + }, + } + metatxs = append(metatxs, metatx) + } + + return metatxs +} + func (n *Node) stopIfRunning() error { if n.Node != nil && n.Node.IsRunning() { if err := n.Node.Stop(); err != nil { @@ -401,17 +473,20 @@ func (n *Node) stopIfRunning() error { } func (n *Node) rebuildNodeFromState(ctx context.Context) error { + start := time.Now() + if n.config.NoReplay { // If NoReplay is true, simply reset the node to its initial state n.logger.Warn("replay disabled") - txs, err := n.pkgs.Load(DefaultFee, n.startTime) + pkgs, err := n.loader.Load(n.paths...) if err != nil { return fmt.Errorf("unable to load pkgs: %w", err) } + genesis := gnoland.DefaultGenState() genesis.Balances = n.config.BalancesList - genesis.Txs = txs + genesis.Txs = n.generateTxs(DefaultFee, pkgs) return n.rebuildNode(ctx, genesis) } @@ -421,7 +496,7 @@ func (n *Node) rebuildNodeFromState(ctx context.Context) error { } // Load genesis packages - pkgsTxs, err := n.pkgs.Load(DefaultFee, n.startTime) + pkgs, err := n.loader.Load(n.paths...) if err != nil { return fmt.Errorf("unable to load pkgs: %w", err) } @@ -429,15 +504,24 @@ func (n *Node) rebuildNodeFromState(ctx context.Context) error { // Create genesis with loaded pkgs + previous state genesis := gnoland.DefaultGenState() genesis.Balances = n.config.BalancesList + + // Generate txs + pkgsTxs := n.generateTxs(DefaultFee, pkgs) genesis.Txs = append(pkgsTxs, state...) // Reset the node with the new genesis state. err = n.rebuildNode(ctx, genesis) - n.logger.Info("reload done", "pkgs", len(pkgsTxs), "state applied", len(state)) + n.logger.Info("reload done", + "pkgs", len(pkgsTxs), + "state applied", len(state), + "took", time.Since(start), + ) // Update node infos + n.pkgs = pkgs n.loadedPackages = len(pkgsTxs) + // Emit reload event n.emitter.Emit(&events.Reload{}) return nil } @@ -534,13 +618,23 @@ func (n *Node) rebuildNode(ctx context.Context, genesis gnoland.GnoGenesisState) func (n *Node) genesisTxResultHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { if !res.IsErr() { + for _, msg := range tx.Msgs { + if addpkg, ok := msg.(vm.MsgAddPackage); ok && addpkg.Package != nil { + n.logger.Debug("add package", + "path", addpkg.Package.Path, + "files", len(addpkg.Package.Files), + "creator", addpkg.Creator.String(), + ) + } + } + return } // XXX: for now, this is only way to catch the error before, after, found := strings.Cut(res.Log, "\n") if !found { - n.logger.Error("unable to send tx", "err", res.Error, "log", res.Log) + n.logger.Error("unable to send tx", "log", res.Log) return } diff --git a/contribs/gnodev/pkg/dev/node_state.go b/contribs/gnodev/pkg/dev/node_state.go index 3f996bc7716..5194653e88a 100644 --- a/contribs/gnodev/pkg/dev/node_state.go +++ b/contribs/gnodev/pkg/dev/node_state.go @@ -84,14 +84,10 @@ func (n *Node) MoveBy(ctx context.Context, x int) error { } // Load genesis packages - pkgsTxs, err := n.pkgs.Load(DefaultFee, n.startTime) - if err != nil { - return fmt.Errorf("unable to load pkgs: %w", err) - } - - newState := n.state[:newIndex] + pkgsTxs := n.generateTxs(DefaultFee, n.pkgs) // Create genesis with loaded pkgs + previous state + newState := n.state[:newIndex] genesis := gnoland.DefaultGenState() genesis.Balances = n.config.BalancesList genesis.Txs = append(pkgsTxs, newState...) @@ -131,7 +127,6 @@ func (n *Node) ExportStateAsGenesis(ctx context.Context) (*bft.GenesisDoc, error // Get current blockstore state doc := *n.Node.GenesisDoc() // copy doc - genState := doc.AppState.(gnoland.GnoGenesisState) genState.Balances = n.config.BalancesList genState.Txs = state diff --git a/contribs/gnodev/pkg/dev/node_state_test.go b/contribs/gnodev/pkg/dev/node_state_test.go index efaeb979693..32800fd0db6 100644 --- a/contribs/gnodev/pkg/dev/node_state_test.go +++ b/contribs/gnodev/pkg/dev/node_state_test.go @@ -6,10 +6,11 @@ import ( "testing" "time" - emitter "github.com/gnolang/gno/contribs/gnodev/internal/mock" + mock "github.com/gnolang/gno/contribs/gnodev/internal/mock/emitter" "github.com/gnolang/gno/contribs/gnodev/pkg/events" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -136,28 +137,29 @@ func TestExportState(t *testing.T) { }) } -func testingCounterRealm(t *testing.T, inc int) (*Node, *emitter.ServerEmitter) { +func testingCounterRealm(t *testing.T, inc int) (*Node, *mock.ServerEmitter) { t.Helper() - const ( - // foo package - counterGnoMod = "module gno.land/r/dev/counter\n" - counterFile = `package counter + const counterFile = ` +package counter + import "strconv" var value int = 0 func Inc(v int) { value += v } // method to increment value func Render(_ string) string { return strconv.Itoa(value) } ` - ) - // Generate package counter - counterPkg := generateTestingPackage(t, - "gno.mod", counterGnoMod, - "foo.gno", counterFile) + counterPkg := gnovm.MemPackage{ + Name: "counter", + Path: "gno.land/r/dev/counter", + Files: []*gnovm.MemFile{ + {Name: "file.gno", Body: counterFile}, + }, + } // Call NewDevNode with no package should work - node, emitter := newTestingDevNode(t, counterPkg) + node, emitter := newTestingDevNode(t, &counterPkg) assert.Len(t, node.ListPkgs(), 1) // Test rendering diff --git a/contribs/gnodev/pkg/dev/node_test.go b/contribs/gnodev/pkg/dev/node_test.go index 38fab0a3360..7da976bc13f 100644 --- a/contribs/gnodev/pkg/dev/node_test.go +++ b/contribs/gnodev/pkg/dev/node_test.go @@ -3,33 +3,28 @@ package dev import ( "context" "encoding/json" - "os" - "path/filepath" "testing" "time" - mock "github.com/gnolang/gno/contribs/gnodev/internal/mock" - + mock "github.com/gnolang/gno/contribs/gnodev/internal/mock/emitter" "github.com/gnolang/gno/contribs/gnodev/pkg/events" + "github.com/gnolang/gno/contribs/gnodev/pkg/packages" "github.com/gnolang/gno/gno.land/pkg/gnoclient" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/integration" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" core_types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/keys" tm2events "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" + tm2std "github.com/gnolang/gno/tm2/pkg/std" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// XXX: We should probably use txtar to test this package. - -var nodeTestingAddress = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - // TestNewNode_NoPackages tests the NewDevNode method with no package. func TestNewNode_NoPackages(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -49,32 +44,35 @@ func TestNewNode_NoPackages(t *testing.T) { } // TestNewNode_WithPackage tests the NewDevNode with a single package. -func TestNewNode_WithPackage(t *testing.T) { +func TestNewNode_WithLoader(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - const ( - // foobar package - testGnoMod = "module gno.land/r/dev/foobar\n" - testFile = `package foobar + pkg := gnovm.MemPackage{ + Name: "foobar", + Path: "gno.land/r/dev/foobar", + Files: []*gnovm.MemFile{ + { + Name: "foobar.gno", + Body: `package foobar func Render(_ string) string { return "foo" } -` - ) +`, + }, + }, + } - // Generate package - pkgpath := generateTestingPackage(t, "gno.mod", testGnoMod, "foobar.gno", testFile) logger := log.NewTestingLogger(t) - // Call NewDevNode with no package should work cfg := DefaultNodeConfig(gnoenv.RootDir(), "gno.land") - cfg.PackagesPathList = []PackagePath{pkgpath} + cfg.Loader = packages.NewLoader(packages.NewMockResolver(&pkg)) cfg.Logger = logger - node, err := NewDevNode(ctx, cfg) + + node, err := NewDevNode(ctx, cfg, pkg.Path) require.NoError(t, err) assert.Len(t, node.ListPkgs(), 1) // Test rendering - render, err := testingRenderRealm(t, node, "gno.land/r/dev/foobar") + render, err := testingRenderRealm(t, node, pkg.Path) require.NoError(t, err) assert.Equal(t, render, "foo") @@ -83,24 +81,37 @@ func Render(_ string) string { return "foo" } func TestNodeAddPackage(t *testing.T) { // Setup a Node instance - const ( - // foo package - fooGnoMod = "module gno.land/r/dev/foo\n" - fooFile = `package foo + fooPkg := gnovm.MemPackage{ + Name: "foo", + Path: "gno.land/r/dev/foo", + Files: []*gnovm.MemFile{ + { + Name: "foo.gno", + Body: `package foo func Render(_ string) string { return "foo" } -` - // bar package - barGnoMod = "module gno.land/r/dev/bar\n" - barFile = `package bar +`, + }, + }, + } + + barPkg := gnovm.MemPackage{ + Name: "bar", + Path: "gno.land/r/dev/bar", + Files: []*gnovm.MemFile{ + { + Name: "bar.gno", + Body: `package bar func Render(_ string) string { return "bar" } -` - ) +`, + }, + }, + } // Generate package foo - foopkg := generateTestingPackage(t, "gno.mod", fooGnoMod, "foo.gno", fooFile) + cfg := newTestingNodeConfig(&fooPkg, &barPkg) // Call NewDevNode with no package should work - node, emitter := newTestingDevNode(t, foopkg) + node, emitter := newTestingDevNodeWithConfig(t, cfg, fooPkg.Path) assert.Len(t, node.ListPkgs(), 1) // Test render @@ -108,54 +119,60 @@ func Render(_ string) string { return "bar" } require.NoError(t, err) require.Equal(t, render, "foo") - // Generate package bar - barpkg := generateTestingPackage(t, "gno.mod", barGnoMod, "bar.gno", barFile) - err = node.UpdatePackages(barpkg.Path) - require.NoError(t, err) - assert.Len(t, node.ListPkgs(), 2) - // Render should fail as the node hasn't reloaded render, err = testingRenderRealm(t, node, "gno.land/r/dev/bar") require.Error(t, err) + // Add bar package + node.AddPackagePaths(barPkg.Path) + err = node.Reload(context.Background()) require.NoError(t, err) assert.Equal(t, emitter.NextEvent().Type(), events.EvtReload) // After a reload, render should succeed - render, err = testingRenderRealm(t, node, "gno.land/r/dev/bar") + render, err = testingRenderRealm(t, node, barPkg.Path) require.NoError(t, err) require.Equal(t, render, "bar") } func TestNodeUpdatePackage(t *testing.T) { - // Setup a Node instance - const ( - // foo package - foobarGnoMod = "module gno.land/r/dev/foobar\n" - fooFile = `package foobar + foorbarPkg := gnovm.MemPackage{ + Name: "foobar", + Path: "gno.land/r/dev/foobar", + } + + fooFiles := []*gnovm.MemFile{ + { + Name: "foo.gno", + Body: `package foobar func Render(_ string) string { return "foo" } -` - barFile = `package foobar +`, + }, + } + + barFiles := []*gnovm.MemFile{ + { + Name: "bar.gno", + Body: `package foobar func Render(_ string) string { return "bar" } -` - ) +`, + }, + } - // Generate package foo - foopkg := generateTestingPackage(t, "gno.mod", foobarGnoMod, "foo.gno", fooFile) + // Update foobar content with bar content + foorbarPkg.Files = fooFiles - // Call NewDevNode with no package should work - node, emitter := newTestingDevNode(t, foopkg) + node, emitter := newTestingDevNode(t, &foorbarPkg) assert.Len(t, node.ListPkgs(), 1) // Test that render is correct - render, err := testingRenderRealm(t, node, "gno.land/r/dev/foobar") + render, err := testingRenderRealm(t, node, foorbarPkg.Path) require.NoError(t, err) require.Equal(t, render, "foo") - // Override `foo.gno` file with bar content - err = os.WriteFile(filepath.Join(foopkg.Path, "foo.gno"), []byte(barFile), 0o700) - require.NoError(t, err) + // Update foobar content with bar content + foorbarPkg.Files = barFiles err = node.Reload(context.Background()) require.NoError(t, err) @@ -164,7 +181,7 @@ func Render(_ string) string { return "bar" } assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) // After a reload, render should succeed - render, err = testingRenderRealm(t, node, "gno.land/r/dev/foobar") + render, err = testingRenderRealm(t, node, foorbarPkg.Path) require.NoError(t, err) require.Equal(t, render, "bar") @@ -172,31 +189,32 @@ func Render(_ string) string { return "bar" } } func TestNodeReset(t *testing.T) { - const ( - // foo package - foobarGnoMod = "module gno.land/r/dev/foo\n" - fooFile = `package foo + fooPkg := gnovm.MemPackage{ + Name: "foo", + Path: "gno.land/r/dev/foo", + Files: []*gnovm.MemFile{ + { + Name: "foo.gno", + Body: `package foo var str string = "foo" func UpdateStr(newStr string) { str = newStr } // method to update 'str' variable func Render(_ string) string { return str } -` - ) - - // Generate package foo - foopkg := generateTestingPackage(t, "gno.mod", foobarGnoMod, "foo.gno", fooFile) +`, + }, + }, + } - // Call NewDevNode with no package should work - node, emitter := newTestingDevNode(t, foopkg) + node, emitter := newTestingDevNode(t, &fooPkg) assert.Len(t, node.ListPkgs(), 1) // Test rendering - render, err := testingRenderRealm(t, node, "gno.land/r/dev/foo") + render, err := testingRenderRealm(t, node, fooPkg.Path) require.NoError(t, err) require.Equal(t, render, "foo") // Call `UpdateStr` to update `str` value with "bar" msg := vm.MsgCall{ - PkgPath: "gno.land/r/dev/foo", + PkgPath: fooPkg.Path, Func: "UpdateStr", Args: []string{"bar"}, Send: nil, @@ -208,7 +226,7 @@ func Render(_ string) string { return str } assert.Equal(t, emitter.NextEvent().Type(), events.EvtTxResult) // Check for correct render update - render, err = testingRenderRealm(t, node, "gno.land/r/dev/foo") + render, err = testingRenderRealm(t, node, fooPkg.Path) require.NoError(t, err) require.Equal(t, render, "bar") @@ -218,18 +236,93 @@ func Render(_ string) string { return str } assert.Equal(t, emitter.NextEvent().Type(), events.EvtReset) // Test rendering should return initial `str` value - render, err = testingRenderRealm(t, node, "gno.land/r/dev/foo") + render, err = testingRenderRealm(t, node, fooPkg.Path) require.NoError(t, err) require.Equal(t, render, "foo") assert.Equal(t, mock.EvtNull, emitter.NextEvent().Type()) } +func TestTxGasFailure(t *testing.T) { + fooPkg := gnovm.MemPackage{ + Name: "foo", + Path: "gno.land/r/dev/foo", + Files: []*gnovm.MemFile{ + { + Name: "foo.gno", + Body: `package foo +import "strconv" + +var i int +func Inc() { i++ } // method to increment i +func Render(_ string) string { return strconv.Itoa(i) } +`, + }, + }, + } + + node, emitter := newTestingDevNode(t, &fooPkg) + assert.Len(t, node.ListPkgs(), 1) + + // Test rendering + render, err := testingRenderRealm(t, node, "gno.land/r/dev/foo") + require.NoError(t, err) + require.Equal(t, "0", render) + + // Call `Inc` to update counter + msg := vm.MsgCall{ + PkgPath: "gno.land/r/dev/foo", + Func: "Inc", + Args: nil, + Send: nil, + } + + res, err := testingCallRealm(t, node, msg) + require.NoError(t, err) + require.NoError(t, res.CheckTx.Error) + require.NoError(t, res.DeliverTx.Error) + assert.Equal(t, emitter.NextEvent().Type(), events.EvtTxResult) + + // Check for correct render update + render, err = testingRenderRealm(t, node, "gno.land/r/dev/foo") + require.NoError(t, err) + require.Equal(t, "1", render) + + // Not Enough gas wanted + callCfg := gnoclient.BaseTxCfg{ + GasFee: ugnot.ValueString(10000), // Gas fee + + // Ensure sufficient gas is provided for the transaction to be committed. + // However, avoid providing too much gas to allow the + // transaction to succeed (OutOfGasError). + GasWanted: 100_000, + } + + res, err = testingCallRealmWithConfig(t, node, callCfg, msg) + require.Error(t, err) + require.ErrorAs(t, err, &tm2std.OutOfGasError{}) + + // Transaction should be committed regardless the error + require.Equal(t, emitter.NextEvent().Type(), events.EvtTxResult, + "(probably) not enough gas for the transaction to be committed") + + // Reload the node + err = node.Reload(context.Background()) + require.NoError(t, err) + assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) + + // Check for correct render update + render, err = testingRenderRealm(t, node, "gno.land/r/dev/foo") + require.NoError(t, err) + + // Assert that the previous transaction hasn't succeeded during genesis reload + require.Equal(t, "1", render) +} + func TestTxTimestampRecover(t *testing.T) { - const ( - // foo package - foobarGnoMod = "module gno.land/r/dev/foo\n" - fooFile = `package foo + const fooFile = ` +package foo + import ( "strconv" "strings" @@ -259,12 +352,35 @@ func Render(_ string) string { return strs.String() } ` - ) + fooPkg := gnovm.MemPackage{ + Name: "foo", + Path: "gno.land/r/dev/foo", + Files: []*gnovm.MemFile{ + { + Name: "foo.gno", + Body: fooFile, + }, + }, + } // Add a hard deadline of 20 seconds to avoid potential deadlock and fail early ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() + // XXX(gfanton): Setting this to `false` somehow makes the time block + // drift from the time spanned by the VM. + cfg := newTestingNodeConfig(&fooPkg) + cfg.TMConfig.Consensus.SkipTimeoutCommit = false + cfg.TMConfig.Consensus.TimeoutCommit = 500 * time.Millisecond + cfg.TMConfig.Consensus.TimeoutPropose = 100 * time.Millisecond + cfg.TMConfig.Consensus.CreateEmptyBlocks = true + + node, emitter := newTestingDevNodeWithConfig(t, cfg, fooPkg.Path) + + render, err := testingRenderRealm(t, node, fooPkg.Path) + require.NoError(t, err) + require.NotEmpty(t, render) + parseJSONTimesList := func(t *testing.T, render string) []time.Time { t.Helper() @@ -282,21 +398,6 @@ func Render(_ string) string { return times } - // Generate package foo - foopkg := generateTestingPackage(t, "gno.mod", foobarGnoMod, "foo.gno", fooFile) - - // Call NewDevNode with no package should work - cfg := createDefaultTestingNodeConfig(foopkg) - - // XXX(gfanton): Setting this to `false` somehow makes the time block - // drift from the time spanned by the VM. - cfg.TMConfig.Consensus.SkipTimeoutCommit = false - cfg.TMConfig.Consensus.TimeoutCommit = 500 * time.Millisecond - cfg.TMConfig.Consensus.TimeoutPropose = 100 * time.Millisecond - cfg.TMConfig.Consensus.CreateEmptyBlocks = true - - node, emitter := newTestingDevNodeWithConfig(t, cfg) - // We need to make sure that blocks are separated by at least 1 second // (minimal time between blocks). We can ensure this by listening for // new blocks and comparing timestamps @@ -329,7 +430,7 @@ func Render(_ string) string { // Span multiple time for i := 0; i < nevents; i++ { - t.Logf("waiting for a bock greater than height(%d) and unix(%d)", refHeight, refTimestamp) + t.Logf("waiting for a block greater than height(%d) and unix(%d)", refHeight, refTimestamp) for { var block types.EventNewBlock select { @@ -357,7 +458,7 @@ func Render(_ string) string { // Span a new time msg := vm.MsgCall{ - PkgPath: "gno.land/r/dev/foo", + PkgPath: fooPkg.Path, Func: "SpanTime", } @@ -373,7 +474,7 @@ func Render(_ string) string { } // Render JSON times list - render, err := testingRenderRealm(t, node, "gno.land/r/dev/foo") + render, err = testingRenderRealm(t, node, fooPkg.Path) require.NoError(t, err) // Parse times list @@ -396,12 +497,12 @@ func Render(_ string) string { assert.Equal(t, emitter.NextEvent().Type(), events.EvtReload) // Fetch time list again from render - render, err = testingRenderRealm(t, node, "gno.land/r/dev/foo") + render, err = testingRenderRealm(t, node, fooPkg.Path) require.NoError(t, err) timesList2 := parseJSONTimesList(t, render) - // Times list should be identical from the orignal list + // Times list should be identical from the original list require.Len(t, timesList2, len(timesList1)) for i := 0; i < len(timesList1); i++ { t1nsec, t2nsec := timesList1[i].UnixNano(), timesList2[i].UnixNano() @@ -430,17 +531,23 @@ func testingRenderRealm(t *testing.T, node *Node, rlmpath string) (string, error func testingCallRealm(t *testing.T, node *Node, msgs ...vm.MsgCall) (*core_types.ResultBroadcastTxCommit, error) { t.Helper() + defaultCfg := gnoclient.BaseTxCfg{ + GasFee: ugnot.ValueString(1000000), // Gas fee + GasWanted: 3_000_000, // Gas wanted + } + + return testingCallRealmWithConfig(t, node, defaultCfg, msgs...) +} + +func testingCallRealmWithConfig(t *testing.T, node *Node, bcfg gnoclient.BaseTxCfg, msgs ...vm.MsgCall) (*core_types.ResultBroadcastTxCommit, error) { + t.Helper() + signer := newInMemorySigner(t, node.Config().ChainID()) cli := gnoclient.Client{ Signer: signer, RPCClient: node.Client(), } - txcfg := gnoclient.BaseTxCfg{ - GasFee: ugnot.ValueString(1000000), // Gas fee - GasWanted: 3_000_000, // Gas wanted - } - // Set Caller in the msgs caller, err := signer.Info() require.NoError(t, err) @@ -449,45 +556,35 @@ func testingCallRealm(t *testing.T, node *Node, msgs ...vm.MsgCall) (*core_types vmMsgs = append(vmMsgs, vm.NewMsgCall(caller.GetAddress(), msg.Send, msg.PkgPath, msg.Func, msg.Args)) } - return cli.Call(txcfg, vmMsgs...) + return cli.Call(bcfg, vmMsgs...) } -func generateTestingPackage(t *testing.T, nameFile ...string) PackagePath { - t.Helper() - workdir := t.TempDir() - - if len(nameFile)%2 != 0 { - require.FailNow(t, "Generate testing packages require paired arguments.") - } - - for i := 0; i < len(nameFile); i += 2 { - name := nameFile[i] - content := nameFile[i+1] - - err := os.WriteFile(filepath.Join(workdir, name), []byte(content), 0o700) - require.NoError(t, err) - } - - return PackagePath{ - Path: workdir, - Creator: nodeTestingAddress, - } -} +func newTestingNodeConfig(pkgs ...*gnovm.MemPackage) *NodeConfig { + var loader packages.BaseLoader + gnoroot := gnoenv.RootDir() -func createDefaultTestingNodeConfig(pkgslist ...PackagePath) *NodeConfig { + loader.Resolver = packages.MiddlewareResolver( + packages.NewMockResolver(pkgs...), + packages.FilterStdlibs) cfg := DefaultNodeConfig(gnoenv.RootDir(), "gno.land") - cfg.PackagesPathList = pkgslist + cfg.TMConfig = integration.DefaultTestingTMConfig(gnoroot) + cfg.Loader = &loader return cfg } -func newTestingDevNode(t *testing.T, pkgslist ...PackagePath) (*Node, *mock.ServerEmitter) { +func newTestingDevNode(t *testing.T, pkgs ...*gnovm.MemPackage) (*Node, *mock.ServerEmitter) { t.Helper() - cfg := createDefaultTestingNodeConfig(pkgslist...) - return newTestingDevNodeWithConfig(t, cfg) + cfg := newTestingNodeConfig(pkgs...) + paths := make([]string, len(pkgs)) + for i, pkg := range pkgs { + paths[i] = pkg.Path + } + + return newTestingDevNodeWithConfig(t, cfg, paths...) } -func newTestingDevNodeWithConfig(t *testing.T, cfg *NodeConfig) (*Node, *mock.ServerEmitter) { +func newTestingDevNodeWithConfig(t *testing.T, cfg *NodeConfig, pkgpaths ...string) (*Node, *mock.ServerEmitter) { t.Helper() ctx, cancel := context.WithCancel(context.Background()) @@ -497,9 +594,9 @@ func newTestingDevNodeWithConfig(t *testing.T, cfg *NodeConfig) (*Node, *mock.Se cfg.Emitter = emitter cfg.Logger = logger - node, err := NewDevNode(ctx, cfg) + node, err := NewDevNode(ctx, cfg, pkgpaths...) require.NoError(t, err) - assert.Len(t, node.ListPkgs(), len(cfg.PackagesPathList)) + require.Equal(t, emitter.NextEvent().Type(), events.EvtReset) t.Cleanup(func() { node.Close() diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go deleted file mode 100644 index 62c1907b8c9..00000000000 --- a/contribs/gnodev/pkg/dev/packages.go +++ /dev/null @@ -1,170 +0,0 @@ -package dev - -import ( - "errors" - "fmt" - "net/url" - "path/filepath" - "time" - - "github.com/gnolang/gno/contribs/gnodev/pkg/address" - "github.com/gnolang/gno/gno.land/pkg/gnoland" - vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/std" -) - -type PackagePath struct { - Path string - Creator crypto.Address - Deposit std.Coins -} - -func ResolvePackagePathQuery(bk *address.Book, path string) (PackagePath, error) { - var ppath PackagePath - - upath, err := url.Parse(path) - if err != nil { - return ppath, fmt.Errorf("malformed path/query: %w", err) - } - ppath.Path = filepath.Clean(upath.Path) - - // Check for creator option - creator := upath.Query().Get("creator") - if creator != "" { - address, err := crypto.AddressFromBech32(creator) - if err != nil { - var ok bool - address, ok = bk.GetByName(creator) - if !ok { - return ppath, fmt.Errorf("invalid name or address for creator %q", creator) - } - } - - ppath.Creator = address - } - - // Check for deposit option - deposit := upath.Query().Get("deposit") - if deposit != "" { - coins, err := std.ParseCoins(deposit) - if err != nil { - return ppath, fmt.Errorf( - "unable to parse deposit amount %q (should be in the form xxxugnot): %w", deposit, err, - ) - } - - ppath.Deposit = coins - } - - return ppath, nil -} - -type Package struct { - gnomod.Pkg - Creator crypto.Address - Deposit std.Coins -} - -type PackagesMap map[string]Package - -var ( - ErrEmptyCreatorPackage = errors.New("no creator specified for package") - ErrEmptyDepositPackage = errors.New("no deposit specified for package") -) - -func NewPackagesMap(ppaths []PackagePath) (PackagesMap, error) { - pkgs := make(map[string]Package) - for _, ppath := range ppaths { - if ppath.Creator.IsZero() { - return nil, fmt.Errorf("unable to load package %q: %w", ppath.Path, ErrEmptyCreatorPackage) - } - - abspath, err := filepath.Abs(ppath.Path) - if err != nil { - return nil, fmt.Errorf("unable to guess absolute path for %q: %w", ppath.Path, err) - } - - // list all packages from target path - pkgslist, err := gnomod.ListPkgs(abspath) - 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] = Package{ - Pkg: pkg, - Creator: ppath.Creator, - Deposit: ppath.Deposit, - } - } - } - - return pkgs, nil -} - -func (pm PackagesMap) toList() gnomod.PkgList { - list := make([]gnomod.Pkg, 0, len(pm)) - for _, pkg := range pm { - list = append(list, pkg.Pkg) - } - return list -} - -func (pm PackagesMap) Load(fee std.Fee, start time.Time) ([]gnoland.TxWithMetadata, error) { - pkgs := pm.toList() - - sorted, err := pkgs.Sort() - if err != nil { - return nil, fmt.Errorf("unable to sort pkgs: %w", err) - } - - nonDraft := sorted.GetNonDraftPkgs() - - metatxs := make([]gnoland.TxWithMetadata, 0, len(nonDraft)) - for _, modPkg := range nonDraft { - pkg := pm[modPkg.Dir] - if pkg.Creator.IsZero() { - return nil, fmt.Errorf("no creator set for %q", pkg.Dir) - } - - // Open files in directory as MemPackage. - memPkg := gno.MustReadMemPackage(modPkg.Dir, modPkg.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: pkg.Creator, - Deposit: pkg.Deposit, - Package: memPkg, - }, - }, - } - - tx.Signatures = make([]std.Signature, len(tx.GetSigners())) - metatx := gnoland.TxWithMetadata{ - Tx: tx, - Metadata: &gnoland.GnoTxMetadata{ - Timestamp: start.Unix(), - }, - } - - metatxs = append(metatxs, metatx) - } - - return metatxs, nil -} diff --git a/contribs/gnodev/pkg/dev/packages_test.go b/contribs/gnodev/pkg/dev/packages_test.go deleted file mode 100644 index 151a89a7815..00000000000 --- a/contribs/gnodev/pkg/dev/packages_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package dev - -import ( - "testing" - - "github.com/gnolang/gno/contribs/gnodev/pkg/address" - "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/std" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestResolvePackagePathQuery(t *testing.T) { - t.Parallel() - - var ( - testingName = "testAccount" - testingAddress = crypto.MustAddressFromString("g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na") - ) - - book := address.NewBook() - book.Add(testingAddress, testingName) - - cases := []struct { - Path string - ExpectedPackagePath PackagePath - ShouldFail bool - }{ - { - Path: ".", - ExpectedPackagePath: PackagePath{ - Path: ".", - }, - }, - { - Path: "/simple/path", - ExpectedPackagePath: PackagePath{ - Path: "/simple/path", - }, - }, - { - Path: "/ambiguo/u//s/path///", - ExpectedPackagePath: PackagePath{ - Path: "/ambiguo/u/s/path", - }, - }, - { - Path: "/path/with/creator?creator=testAccount", - ExpectedPackagePath: PackagePath{ - Path: "/path/with/creator", - Creator: testingAddress, - }, - }, - { - Path: "/path/with/deposit?deposit=" + ugnot.ValueString(100), - ExpectedPackagePath: PackagePath{ - Path: "/path/with/deposit", - Deposit: std.MustParseCoins(ugnot.ValueString(100)), - }, - }, - { - Path: ".?creator=g1hr3dl82qdy84a5h3dmckh0suc7zgwm5rnns6na&deposit=" + ugnot.ValueString(100), - ExpectedPackagePath: PackagePath{ - Path: ".", - Creator: testingAddress, - Deposit: std.MustParseCoins(ugnot.ValueString(100)), - }, - }, - - // errors cases - { - Path: "/invalid/account?creator=UnknownAccount", - ShouldFail: true, - }, - { - Path: "/invalid/address?creator=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - ShouldFail: true, - }, - { - Path: "/invalid/deposit?deposit=abcd", - ShouldFail: true, - }, - } - - for _, tc := range cases { - tc := tc - t.Run(tc.Path, func(t *testing.T) { - t.Parallel() - - result, err := ResolvePackagePathQuery(book, tc.Path) - if tc.ShouldFail { - assert.Error(t, err) - return - } - require.NoError(t, err) - - assert.Equal(t, tc.ExpectedPackagePath.Path, result.Path) - assert.Equal(t, tc.ExpectedPackagePath.Creator, result.Creator) - assert.Equal(t, tc.ExpectedPackagePath.Deposit.String(), result.Deposit.String()) - }) - } -} diff --git a/contribs/gnodev/pkg/dev/query_path.go b/contribs/gnodev/pkg/dev/query_path.go new file mode 100644 index 00000000000..b33576ccc71 --- /dev/null +++ b/contribs/gnodev/pkg/dev/query_path.go @@ -0,0 +1,58 @@ +package dev + +import ( + "fmt" + "net/url" + "path" + + "github.com/gnolang/gno/contribs/gnodev/pkg/address" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" +) + +type QueryPath struct { + Path string + Creator crypto.Address + Deposit std.Coins +} + +func ResolveQueryPath(bk *address.Book, query string) (QueryPath, error) { + var qpath QueryPath + + upath, err := url.Parse(query) + if err != nil { + return qpath, fmt.Errorf("malformed path/query: %w", err) + } + + qpath.Path = path.Clean(upath.Path) + + // Check for creator option + creator := upath.Query().Get("creator") + if creator != "" { + address, err := crypto.AddressFromBech32(creator) + if err != nil { + var ok bool + address, ok = bk.GetByName(creator) + if !ok { + return qpath, fmt.Errorf("invalid name or address for creator %q", creator) + } + } + + qpath.Creator = address + } + + // Check for deposit option + deposit := upath.Query().Get("deposit") + if deposit != "" { + coins, err := std.ParseCoins(deposit) + if err != nil { + return qpath, fmt.Errorf( + "unable to parse deposit amount %q (should be in the form xxxugnot): %w", deposit, err, + ) + } + + qpath.Deposit = coins + } + + return qpath, nil +} diff --git a/contribs/gnodev/pkg/dev/query_path_test.go b/contribs/gnodev/pkg/dev/query_path_test.go new file mode 100644 index 00000000000..519edcc7739 --- /dev/null +++ b/contribs/gnodev/pkg/dev/query_path_test.go @@ -0,0 +1,132 @@ +package dev_test + +import ( + "testing" + + "github.com/gnolang/gno/contribs/gnodev/pkg/address" + "github.com/gnolang/gno/contribs/gnodev/pkg/dev" + "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestResolvePackageModifierQuery(t *testing.T) { + validAddr := crypto.MustAddressFromString(integration.DefaultAccount_Address) + validBech32Addr := validAddr.String() + validCoins := std.MustParseCoins("100ugnot") + + tests := []struct { + name string + path string + book *address.Book + wantQuery dev.QueryPath + wantErrMsg string + }{ + { + name: "valid creator bech32", + path: "abc.xy/some/path?creator=" + validBech32Addr, + book: address.NewBook(), + wantQuery: dev.QueryPath{ + Path: "abc.xy/some/path", + Creator: validAddr, + }, + }, + + { + name: "valid creator name", + path: "abc.xy/path?creator=alice", + book: func() *address.Book { + bk := address.NewBook() + bk.Add(validAddr, "alice") + return bk + }(), + wantQuery: dev.QueryPath{ + Path: "abc.xy/path", + Creator: validAddr, + }, + }, + + { + name: "invalid creator", + path: "abc.xy/path?creator=bob", + book: address.NewBook(), + wantErrMsg: `invalid name or address for creator "bob"`, + }, + + { + name: "invalid bech32 creator", + path: "abc.xy/path?creator=invalid", + book: address.NewBook(), + wantErrMsg: `invalid name or address for creator "invalid"`, + }, + + { + name: "valid deposit", + path: "abc.xy/path?deposit=100ugnot", + book: address.NewBook(), + wantQuery: dev.QueryPath{ + Path: "abc.xy/path", + Deposit: validCoins, + }, + }, + + { + name: "invalid deposit", + path: "abc.xy/path?deposit=invalid", + book: address.NewBook(), + wantErrMsg: `unable to parse deposit amount "invalid" (should be in the form xxxugnot)`, + }, + + { + name: "both creator and deposit", + path: "abc.xy/path?creator=" + validBech32Addr + "&deposit=100ugnot", + book: address.NewBook(), + wantQuery: dev.QueryPath{ + Path: "abc.xy/path", + Creator: validAddr, + Deposit: validCoins, + }, + }, + + { + name: "malformed path", + path: "://invalid", + book: address.NewBook(), + wantErrMsg: "malformed path/query", + }, + + { + name: "no creator or deposit", + path: "abc.xy/path", + book: address.NewBook(), + wantQuery: dev.QueryPath{ + Path: "abc.xy/path", + }, + }, + + { + name: "clean path with ..", + path: "abc.xy/foo/../bar", + book: address.NewBook(), + wantQuery: dev.QueryPath{ + Path: "abc.xy/bar", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotQuery, err := dev.ResolveQueryPath(tt.book, tt.path) + if tt.wantErrMsg != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErrMsg) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.wantQuery, gotQuery) + }) + } +} diff --git a/contribs/gnodev/pkg/emitter/middleware.go b/contribs/gnodev/pkg/emitter/middleware.go index 9c53cfe158e..e4def43f919 100644 --- a/contribs/gnodev/pkg/emitter/middleware.go +++ b/contribs/gnodev/pkg/emitter/middleware.go @@ -5,10 +5,10 @@ import ( _ "embed" "encoding/json" "fmt" + "html/template" "net/http" "strings" "sync" - "text/template" "github.com/gnolang/gno/contribs/gnodev/pkg/events" ) diff --git a/contribs/gnodev/pkg/emitter/middleware_test.go b/contribs/gnodev/pkg/emitter/middleware_test.go new file mode 100644 index 00000000000..bed7ddd0e75 --- /dev/null +++ b/contribs/gnodev/pkg/emitter/middleware_test.go @@ -0,0 +1,43 @@ +package emitter + +import ( + "fmt" + "net/http" + "net/http/httptest" + "regexp" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMiddlewareUsesHTMLTemplate(t *testing.T) { + tests := []struct { + name string + remote string + want string + }{ + {"normal remote", "localhost:9999", "const ws = new WebSocket('ws://localhost:9999');"}, + {"xss'd remote", `localhost:9999');alert('pwned`, "const ws = new WebSocket('ws://localhost:9999');alert('pwned');"}, + } + + // As the code revolves, add more search patterns here. + reWebsocket := regexp.MustCompile("const ws = new WebSocket[^\n]+") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rec := httptest.NewRecorder() + mdw := NewMiddleware(tt.remote, http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Content-Type", "text/html") + fmt.Fprintf(rw, "") + })) + rec.Header().Set("Content-Type", "text/html") + req := httptest.NewRequest("GET", "https://gno.land/example", nil) + mdw.ServeHTTP(rec, req) + + targets := reWebsocket.FindAllString(rec.Body.String(), -1) + require.True(t, len(targets) > 0) + body := targets[0] + require.Equal(t, body, tt.want) + }) + } +} diff --git a/contribs/gnodev/pkg/emitter/server.go b/contribs/gnodev/pkg/emitter/server.go index 3e32984268d..29f8e3050ba 100644 --- a/contribs/gnodev/pkg/emitter/server.go +++ b/contribs/gnodev/pkg/emitter/server.go @@ -1,6 +1,7 @@ package emitter import ( + "encoding/json" "log/slog" "net/http" "sync" @@ -32,6 +33,10 @@ func NewServer(logger *slog.Logger) *Server { } } +func (s *Server) LockEmit() { s.muClients.Lock() } + +func (s *Server) UnlockEmit() { s.muClients.Unlock() } + // ws handler func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { conn, err := s.upgrader.Upgrade(w, r, nil) @@ -69,13 +74,9 @@ func (s *Server) emit(evt events.Event) { s.muClients.Lock() defer s.muClients.Unlock() - jsonEvt := EventJSON{evt.Type(), evt} - - s.logger.Info("sending event to clients", - "clients", len(s.clients), - "type", evt.Type(), - "event", evt) + s.logEvent(evt) + jsonEvt := EventJSON{evt.Type(), evt} for conn := range s.clients { err := conn.WriteJSON(jsonEvt) if err != nil { @@ -96,3 +97,15 @@ func (s *Server) conns() []*websocket.Conn { return conns } + +func (s *Server) logEvent(evt events.Event) { + var logEvt string + if rawEvt, err := json.Marshal(evt); err == nil { + logEvt = string(rawEvt) + } + + s.logger.Info("sending event to clients", + "clients", len(s.clients), + "type", evt.Type(), + "event", logEvt) +} diff --git a/contribs/gnodev/pkg/emitter/static/hotreload.js b/contribs/gnodev/pkg/emitter/static/hotreload.js index aabad4f341c..7b58fc35004 100644 --- a/contribs/gnodev/pkg/emitter/static/hotreload.js +++ b/contribs/gnodev/pkg/emitter/static/hotreload.js @@ -1,17 +1,34 @@ -(function() { +document.addEventListener('DOMContentLoaded', function() { // Define the events that will trigger a page reload - const eventsReload = {{ .ReloadEvents | json }}; - + const eventsReload = [ + {{range .ReloadEvents}}'{{.}}',{{end}} + ]; + // Establish the WebSocket connection to the event server const ws = new WebSocket('ws://{{- .Remote -}}'); - + // `gracePeriod` mitigates reload loops due to excessive events. This period // occurs post-loading and lasts for the `graceTimeout` duration. const graceTimeout = 1000; // ms let gracePeriod = true; let debounceTimeout = setTimeout(function() { gracePeriod = false; - }, graceTimeout); + }, graceTimeout); + + // Flag to track if a link click is in progress + let clickInProgress = false; + + // Capture clicks on tags to prevent reloading appening when clicking on link + document.addEventListener('click', function(event) { + const target = event.target; + if (target.tagName === 'A' && target.href) { + clickInProgress = true; + // Wait a bit before allowing reload again + setTimeout(function() { + clickInProgress = false; + }, 5000); + } + }); // Handle incoming WebSocket messages ws.onmessage = function(event) { @@ -21,19 +38,21 @@ // Ignore events not in the reload-triggering list if (!eventsReload.includes(message.type)) { - return; + return; } - // Reload the page immediately if we're not in the grace period - if (!gracePeriod) { + // Reload the page immediately if we're not in the grace period and no clicks are in progress + if (!gracePeriod && !clickInProgress) { window.location.reload(); return; } - // If still in the grace period, debounce the reload + // If still in the grace period or a click is in progress, debounce the reload clearTimeout(debounceTimeout); debounceTimeout = setTimeout(function() { - window.location.reload(); + if (!clickInProgress) { + window.location.reload(); + } }, graceTimeout); } catch (e) { @@ -48,4 +67,4 @@ ws.onclose = function() { console.log('WebSocket connection closed'); }; -})(); +}); diff --git a/contribs/gnodev/pkg/events/events.go b/contribs/gnodev/pkg/events/events.go index c387c331eed..3137b5d50bb 100644 --- a/contribs/gnodev/pkg/events/events.go +++ b/contribs/gnodev/pkg/events/events.go @@ -41,8 +41,8 @@ type PackagesUpdate struct { } type PackageUpdate struct { - Package string `json:"package"` - Files []string `json:"files"` + PackageDir string `json:"package"` + Files []string `json:"files"` } func (PackagesUpdate) Type() Type { return EvtPackagesUpdate } diff --git a/contribs/gnodev/pkg/logger/log_column.go b/contribs/gnodev/pkg/logger/log_column.go index 2a720525903..0e6c181ad6d 100644 --- a/contribs/gnodev/pkg/logger/log_column.go +++ b/contribs/gnodev/pkg/logger/log_column.go @@ -21,7 +21,9 @@ func NewColumnLogger(w io.Writer, level slog.Level, profile termenv.Profile) *Co }) // Default column output - defaultOutput := newColumeWriter(lipgloss.NewStyle(), "", w) + renderer := lipgloss.NewRenderer(nil, termenv.WithProfile(profile)) + + defaultOutput := newColumeWriter(w, lipgloss.NewStyle(), "") charmLogger.SetOutput(defaultOutput) charmLogger.SetStyles(defaultStyles()) charmLogger.SetColorProfile(profile) @@ -40,10 +42,12 @@ func NewColumnLogger(w io.Writer, level slog.Level, profile termenv.Profile) *Co } return &ColumnLogger{ - Logger: charmLogger, - writer: w, - prefix: charmLogger.GetPrefix(), - colors: map[string]lipgloss.Color{}, + Logger: charmLogger, + writer: w, + prefix: charmLogger.GetPrefix(), + colors: map[string]lipgloss.Color{}, + colorProfile: profile, + renderer: renderer, } } @@ -52,6 +56,7 @@ type ColumnLogger struct { prefix string writer io.Writer + renderer *lipgloss.Renderer colorProfile termenv.Profile colors map[string]lipgloss.Color @@ -72,10 +77,11 @@ func (cl *ColumnLogger) WithGroup(group string) slog.Handler { // generate bright color based on the group name fg = colorFromString(group, 0.5, 0.6) } - baseStyle := lipgloss.NewStyle().Foreground(fg) + + baseStyle := lipgloss.NewStyle().Foreground(fg).Renderer(cl.renderer) nlog := cl.Logger.With() // clone logger - nlog.SetOutput(newColumeWriter(baseStyle, group, cl.writer)) + nlog.SetOutput(newColumeWriter(cl.writer, baseStyle, group)) nlog.SetColorProfile(cl.colorProfile) return &ColumnLogger{ Logger: nlog, @@ -99,7 +105,7 @@ type columnWriter struct { writer io.Writer } -func newColumeWriter(baseStyle lipgloss.Style, prefix string, writer io.Writer) *columnWriter { +func newColumeWriter(w io.Writer, baseStyle lipgloss.Style, prefix string) *columnWriter { const width = 12 style := baseStyle. @@ -112,7 +118,7 @@ func newColumeWriter(baseStyle lipgloss.Style, prefix string, writer io.Writer) prefix = prefix[:width-3] + "..." } - return &columnWriter{style: style, prefix: prefix, writer: writer} + return &columnWriter{style: style, prefix: prefix, writer: w} } func (cl *columnWriter) Write(buf []byte) (n int, err error) { diff --git a/contribs/gnodev/pkg/packages/glob.go b/contribs/gnodev/pkg/packages/glob.go new file mode 100644 index 00000000000..1b76425deb4 --- /dev/null +++ b/contribs/gnodev/pkg/packages/glob.go @@ -0,0 +1,214 @@ +// Inspired by: https://cs.opensource.google/go/x/tools/+/master:gopls/internal/test/integration/fake/glob/glob.go + +package packages + +import ( + "errors" + "fmt" + "strings" +) + +var ErrAdjacentSlash = errors.New("** may only be adjacent to '/'") + +// Glob patterns can have the following syntax: +// - `*` to match one or more characters in a path segment +// - `**` to match any number of path segments, including none +// +// Expanding on this: +// - '/' matches one or more literal slashes. +// - any other character matches itself literally. +type Glob struct { + elems []element // pattern elements +} + +// Parse builds a Glob for the given pattern, returning an error if the pattern +// is invalid. +func Parse(pattern string) (*Glob, error) { + g, _, err := parse(pattern) + return g, err +} + +func parse(pattern string) (*Glob, string, error) { + g := new(Glob) + for len(pattern) > 0 { + switch pattern[0] { + case '/': + // Skip consecutive slashes + for len(pattern) > 0 && pattern[0] == '/' { + pattern = pattern[1:] + } + g.elems = append(g.elems, slash{}) + + case '*': + if len(pattern) > 1 && pattern[1] == '*' { + if (len(g.elems) > 0 && g.elems[len(g.elems)-1] != slash{}) || (len(pattern) > 2 && pattern[2] != '/') { + return nil, "", ErrAdjacentSlash + } + pattern = pattern[2:] + g.elems = append(g.elems, starStar{}) + break + } + pattern = pattern[1:] + g.elems = append(g.elems, star{}) + + default: + pattern = g.parseLiteral(pattern) + } + } + return g, "", nil +} + +func (g *Glob) parseLiteral(pattern string) string { + end := strings.IndexAny(pattern, "*/") + if end == -1 { + end = len(pattern) + } + g.elems = append(g.elems, literal(pattern[:end])) + return pattern[end:] +} + +func (g *Glob) String() string { + var b strings.Builder + for _, e := range g.elems { + fmt.Fprint(&b, e) + } + return b.String() +} + +func (g *Glob) StarFreeBase() string { + var b strings.Builder + for _, e := range g.elems { + if e == (star{}) || e == (starStar{}) { + break + } + fmt.Fprint(&b, e) + } + return b.String() +} + +// element holds a glob pattern element, as defined below. +type element fmt.Stringer + +// element types. +type ( + slash struct{} // One or more '/' separators + literal string // string literal, not containing / or * + star struct{} // * + starStar struct{} // ** +) + +func (s slash) String() string { return "/" } +func (l literal) String() string { return string(l) } +func (s star) String() string { return "*" } +func (s starStar) String() string { return "**" } + +// Match reports whether the input string matches the glob pattern. +func (g *Glob) Match(input string) bool { + return match(g.elems, input) +} + +func match(elems []element, input string) (ok bool) { + var elem interface{} + for len(elems) > 0 { + elem, elems = elems[0], elems[1:] + switch elem := elem.(type) { + case slash: + // Skip consecutive slashes in the input + if len(input) == 0 || input[0] != '/' { + return false + } + for len(input) > 0 && input[0] == '/' { + input = input[1:] + } + + case starStar: + // Special cases: + // - **/a matches "a" + // - **/ matches everything + // + // Note that if ** is followed by anything, it must be '/' (this is + // enforced by Parse). + if len(elems) > 0 { + elems = elems[1:] + } + + // A trailing ** matches anything. + if len(elems) == 0 { + return true + } + + // Backtracking: advance pattern segments until the remaining pattern + // elements match. + for len(input) != 0 { + if match(elems, input) { + return true + } + _, input = split(input) + } + return false + + case literal: + if !strings.HasPrefix(input, string(elem)) { + return false + } + input = input[len(elem):] + + case star: + var segInput string + segInput, input = split(input) + + elemEnd := len(elems) + for i, e := range elems { + if e == (slash{}) { + elemEnd = i + break + } + } + segElems := elems[:elemEnd] + elems = elems[elemEnd:] + + // A trailing * matches the entire segment. + if len(segElems) == 0 { + if len(elems) > 0 && elems[0] == (slash{}) { + elems = elems[1:] // shift elems + } + break + } + + // Backtracking: advance characters until remaining subpattern elements + // match. + matched := false + for i := range segInput { + if match(segElems, segInput[i:]) { + matched = true + break + } + } + if !matched { + return false + } + + default: + panic(fmt.Sprintf("segment type %T not implemented", elem)) + } + } + + return len(input) == 0 +} + +// split returns the portion before and after the first slash +// (or sequence of consecutive slashes). If there is no slash +// it returns (input, nil). +func split(input string) (first, rest string) { + i := strings.IndexByte(input, '/') + if i < 0 { + return input, "" + } + first = input[:i] + for j := i; j < len(input); j++ { + if input[j] != '/' { + return first, input[j:] + } + } + return first, "" +} diff --git a/contribs/gnodev/pkg/packages/glob_test.go b/contribs/gnodev/pkg/packages/glob_test.go new file mode 100644 index 00000000000..7fad4eb2fe1 --- /dev/null +++ b/contribs/gnodev/pkg/packages/glob_test.go @@ -0,0 +1,93 @@ +// Inspired by: https://cs.opensource.google/go/x/tools/+/master:gopls/internal/test/integration/fake/glob/glob_test.go + +package packages + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMatch(t *testing.T) { + t.Parallel() + + tests := []struct { + pattern, input string + want bool + }{ + // Basic cases. + {"", "", true}, + {"", "a", false}, + {"", "/", false}, + {"abc", "abc", true}, + + // ** behavior + {"**", "abc", true}, + {"**/abc", "abc", true}, + {"**", "abc/def", true}, + + // * behavior + {"/*", "/a", true}, + {"*", "foo", true}, + {"*o", "foo", true}, + {"*o", "foox", false}, + {"f*o", "foo", true}, + {"f*o", "fo", true}, + + // Dirs cases + {"**/", "path/to/foo/", true}, + {"**/", "path/to/foo", true}, + + {"path/to/foo", "path/to/foo", true}, + {"path/to/foo", "path/to/bar", false}, + {"path/*/foo", "path/to/foo", true}, + {"path/*/1/*/3/*/5*/foo", "path/to/1/2/3/4/522/foo", true}, + {"path/*/1/*/3/*/5*/foo", "path/to/1/2/3/4/722/foo", false}, + {"path/*/1/*/3/*/5*/foo", "path/to/1/2/3/4/522/bar", false}, + {"path/*/foo", "path/to/to/foo", false}, + {"path/**/foo", "path/to/to/foo", true}, + {"path/**/foo", "path/to/to/bar", false}, + {"path/**/foo", "path/foo", true}, + {"**/abc/**", "foo/r/x/abc/bar", true}, + + // Realistic examples. + {"**/*.ts", "path/to/foo.ts", true}, + {"**/*.js", "path/to/foo.js", true}, + {"**/*.go", "path/to/foo.go", true}, + } + + for _, test := range tests { + g, err := Parse(test.pattern) + require.NoErrorf(t, err, "Parse(%q) failed unexpectedly: %v", test.pattern, err) + assert.Equalf(t, test.want, g.Match(test.input), + "Parse(%q).Match(%q) = %t, want %t", test.pattern, test.input, !test.want, test.want) + } +} + +func TestBaseFreeStar(t *testing.T) { + t.Parallel() + + tests := []struct { + pattern, baseFree string + }{ + // Basic cases. + {"", ""}, + {"foo", "foo"}, + {"foo/bar", "foo/bar"}, + {"foo///bar", "foo/bar"}, + {"foo/bar/", "foo/bar/"}, + {"foo/bar/*/*/z", "foo/bar/"}, + {"foo/bar/**", "foo/bar/"}, + {"**", ""}, + {"/**", "/"}, + } + + for _, test := range tests { + g, err := Parse(test.pattern) + require.NoErrorf(t, err, "Parse(%q) failed unexpectedly: %v", test.pattern, err) + got := g.StarFreeBase() + assert.Equalf(t, test.baseFree, got, + "Parse(%q).Match(%q) = %q, want %q", test.pattern, test.baseFree, got, test.baseFree) + } +} diff --git a/contribs/gnodev/pkg/packages/loader.go b/contribs/gnodev/pkg/packages/loader.go new file mode 100644 index 00000000000..3bc978721e6 --- /dev/null +++ b/contribs/gnodev/pkg/packages/loader.go @@ -0,0 +1,12 @@ +package packages + +type Loader interface { + // Load resolves package package paths and all their dependencies in the correct order. + Load(paths ...string) ([]Package, error) + + // Resolve processes a single package path and returns the corresponding Package. + Resolve(path string) (*Package, error) + + // Name of the loader + Name() string +} diff --git a/contribs/gnodev/pkg/packages/loader_base.go b/contribs/gnodev/pkg/packages/loader_base.go new file mode 100644 index 00000000000..039932bd400 --- /dev/null +++ b/contribs/gnodev/pkg/packages/loader_base.go @@ -0,0 +1,104 @@ +package packages + +import ( + "errors" + "fmt" + "go/parser" + "go/token" +) + +type BaseLoader struct { + Resolver +} + +func NewLoader(res ...Resolver) *BaseLoader { + return &BaseLoader{ChainResolvers(res...)} +} + +func (l BaseLoader) Name() string { + return l.Resolver.Name() +} + +func (l BaseLoader) Load(paths ...string) ([]Package, error) { + fset := token.NewFileSet() + visited, stack := map[string]bool{}, map[string]bool{} + pkgs := make([]Package, 0) + for _, root := range paths { + deps, err := load(root, fset, l.Resolver, visited, stack) + if err != nil { + return nil, err + } + pkgs = append(pkgs, deps...) + } + + return pkgs, nil +} + +func (l BaseLoader) Resolve(path string) (*Package, error) { + fset := token.NewFileSet() + return l.Resolver.Resolve(fset, path) +} + +func load(path string, fset *token.FileSet, resolver Resolver, visited, stack map[string]bool) ([]Package, error) { + if stack[path] { + return nil, fmt.Errorf("cycle detected: %s", path) + } + if visited[path] { + return nil, nil + } + + visited[path] = true + + mempkg, err := resolver.Resolve(fset, path) + if err != nil { + if errors.Is(err, ErrResolverPackageSkip) { + return nil, nil + } + + return nil, fmt.Errorf("unable to resolve package %q: %w", path, err) + } + + var name string + imports := map[string]struct{}{} + for _, file := range mempkg.Files { + fname := file.Name + if !isGnoFile(fname) || isTestFile(fname) { + continue + } + + f, err := parser.ParseFile(fset, fname, file.Body, parser.ImportsOnly) + if err != nil { + return nil, fmt.Errorf("unable to parse file %q: %w", file.Name, err) + } + + if name != "" && name != f.Name.Name { + return nil, fmt.Errorf("conflict package name between %q and %q", name, f.Name.Name) + } + + for _, imp := range f.Imports { + if len(imp.Path.Value) <= 2 { + continue + } + + val := imp.Path.Value[1 : len(imp.Path.Value)-1] + imports[val] = struct{}{} + } + + name = f.Name.Name + } + + pkgs := []Package{} + for imp := range imports { + subDeps, err := load(imp, fset, resolver, visited, stack) + if err != nil { + return nil, fmt.Errorf("importing %q: %w", imp, err) + } + + pkgs = append(pkgs, subDeps...) + } + pkgs = append(pkgs, *mempkg) + + stack[path] = false + + return pkgs, nil +} diff --git a/contribs/gnodev/pkg/packages/loader_glob.go b/contribs/gnodev/pkg/packages/loader_glob.go new file mode 100644 index 00000000000..6e3d1158cb5 --- /dev/null +++ b/contribs/gnodev/pkg/packages/loader_glob.go @@ -0,0 +1,97 @@ +package packages + +import ( + "fmt" + "go/token" + "io/fs" + "os" + "path" + "path/filepath" + "strings" +) + +type GlobLoader struct { + Root string + Resolver Resolver +} + +func NewGlobLoader(rootpath string, res ...Resolver) *GlobLoader { + return &GlobLoader{rootpath, ChainResolvers(res...)} +} + +func (l GlobLoader) Name() string { + return l.Resolver.Name() +} + +func (l GlobLoader) MatchPaths(globs ...string) ([]string, error) { + if l.Root == "" { + return globs, nil + } + + if _, err := os.Stat(l.Root); err != nil { + return nil, fmt.Errorf("unable to stat root: %w", err) + } + + mpaths := []string{} + for _, input := range globs { + cleanInput := path.Clean(input) + gpath, err := Parse(cleanInput) + if err != nil { + return nil, fmt.Errorf("invalid glob path %q: %w", input, err) + } + + base := gpath.StarFreeBase() + if base == cleanInput { + mpaths = append(mpaths, base) + continue + } + + root := l.Root + err = filepath.WalkDir(root, func(dirpath string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + path, err := filepath.Rel(root, dirpath) + if err != nil { + return err + } + + // normalize filepath to path + path = NormalizeFilepathToPath(path) + + if !d.IsDir() { + return nil + } + + if strings.HasPrefix(d.Name(), ".") { + return fs.SkipDir + } + + if gpath.Match(path) { + mpaths = append(mpaths, path) + } + + return nil + }) + if err != nil { + return nil, fmt.Errorf("walking directory %q: %w", root, err) + } + } + + return mpaths, nil +} + +func (l GlobLoader) Load(gpaths ...string) ([]Package, error) { + paths, err := l.MatchPaths(gpaths...) + if err != nil { + return nil, fmt.Errorf("match glob pattern error: %w", err) + } + + loader := &BaseLoader{Resolver: l.Resolver} + return loader.Load(paths...) +} + +func (l GlobLoader) Resolve(path string) (*Package, error) { + return l.Resolver.Resolve(token.NewFileSet(), path) +} diff --git a/contribs/gnodev/pkg/packages/loader_test.go b/contribs/gnodev/pkg/packages/loader_test.go new file mode 100644 index 00000000000..1fa338587b0 --- /dev/null +++ b/contribs/gnodev/pkg/packages/loader_test.go @@ -0,0 +1,83 @@ +package packages + +import ( + "testing" + + "github.com/gnolang/gno/gnovm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLoader_LoadWithDeps(t *testing.T) { + t.Parallel() + + fsresolver := NewRootResolver("./testdata") + loader := NewLoader(fsresolver) + + // package c depend on package b + pkgs, err := loader.Load(TestdataPkgC) + require.NoError(t, err) + require.Len(t, pkgs, 3) + for i, path := range []string{TestdataPkgA, TestdataPkgB, TestdataPkgC} { + assert.Equal(t, path, pkgs[i].Path) + } +} + +func TestLoader_ResolverPriority(t *testing.T) { + t.Parallel() + + const commonPath = "abc.yz/pkg/a" + + pkgA := gnovm.MemPackage{Name: "pkga", Path: commonPath} + resolverA := NewMockResolver(&pkgA) + + pkgB := gnovm.MemPackage{Name: "pkgb", Path: commonPath} + resolverB := NewMockResolver(&pkgB) + + t.Run("pkgA then pkgB", func(t *testing.T) { + t.Parallel() + + loader := NewLoader(resolverA, resolverB) + pkg, err := loader.Resolve(commonPath) + require.NoError(t, err) + require.Equal(t, pkgA.Name, pkg.Name) + require.Equal(t, commonPath, pkg.Path) + }) + + t.Run("pkgB then pkgA", func(t *testing.T) { + t.Parallel() + + loader := NewLoader(resolverB, resolverA) + pkg, err := loader.Resolve(commonPath) + require.NoError(t, err) + require.Equal(t, pkgB.Name, pkg.Name) + require.Equal(t, commonPath, pkg.Path) + }) +} + +func TestLoader_Glob(t *testing.T) { + const root = "./testdata" + cases := []struct { + GlobPath string + PkgResults []string + }{ + {"abc.xy/pkg/*", []string{TestdataPkgA, TestdataPkgB, TestdataPkgC}}, + {"abc.xy/nested/*", []string{TestdataNestedA}}, + {"abc.xy/**/cc", []string{TestdataNestedC, TestdataPkgA, TestdataPkgB, TestdataPkgC}}, + {"abc.xy/*/aa", []string{TestdataNestedA, TestdataPkgA}}, + } + + fsresolver := NewRootResolver("./testdata") + globloader := NewGlobLoader("./testdata", fsresolver) + + for _, tc := range cases { + t.Run(tc.GlobPath, func(t *testing.T) { + pkgs, err := globloader.Load(tc.GlobPath) + require.NoError(t, err) + require.Len(t, pkgs, len(tc.PkgResults)) + for i, expected := range tc.PkgResults { + assert.Equal(t, expected, pkgs[i].Path) + } + }) + } +} diff --git a/contribs/gnodev/pkg/packages/package.go b/contribs/gnodev/pkg/packages/package.go new file mode 100644 index 00000000000..d6aa532ce64 --- /dev/null +++ b/contribs/gnodev/pkg/packages/package.go @@ -0,0 +1,102 @@ +package packages + +import ( + "fmt" + "go/parser" + "go/token" + "os" + "path/filepath" + + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" +) + +type PackageKind int + +const ( + PackageKindOther = iota + PackageKindRemote = iota + PackageKindFS +) + +type Package struct { + gnovm.MemPackage + Kind PackageKind + Location string +} + +func ReadPackageFromDir(fset *token.FileSet, path, dir string) (*Package, error) { + modpath := filepath.Join(dir, "gno.mod") + if _, err := os.Stat(modpath); err == nil { + draft, err := isDraftFile(modpath) + if err != nil { + return nil, err + } + + // Skip draft package + // XXX: We could potentially do that in a middleware, but doing this + // here avoid to potentially parse broken files + if draft { + return nil, ErrResolverPackageSkip + } + } + + mempkg, err := gnolang.ReadMemPackage(dir, path) + switch { + case err == nil: // ok + case os.IsNotExist(err): + return nil, ErrResolverPackageNotFound + default: + return nil, fmt.Errorf("unable to read package %q: %w", dir, err) + } + + if err := validateMemPackage(fset, mempkg); err != nil { + return nil, err + } + + return &Package{ + MemPackage: *mempkg, + Location: dir, + Kind: PackageKindFS, + }, nil +} + +func validateMemPackage(fset *token.FileSet, mempkg *gnovm.MemPackage) error { + if mempkg.IsEmpty() { + return fmt.Errorf("empty package: %w", ErrResolverPackageSkip) + } + + // Validate package name + for _, file := range mempkg.Files { + if !isGnoFile(file.Name) || isTestFile(file.Name) { + continue + } + + f, err := parser.ParseFile(fset, file.Name, file.Body, parser.PackageClauseOnly) + if err != nil { + return fmt.Errorf("unable to parse file %q: %w", file.Name, err) + } + + if f.Name.Name != mempkg.Name { + return fmt.Errorf("%q package name conflict, expected %q found %q", + mempkg.Path, mempkg.Name, f.Name.Name) + } + } + + return nil +} + +func isDraftFile(modpath string) (bool, error) { + modfile, err := os.ReadFile(modpath) + if err != nil { + return false, fmt.Errorf("unable to read file %q: %w", modpath, err) + } + + mod, err := gnomod.Parse(modpath, modfile) + if err != nil { + return false, fmt.Errorf("unable to parse `gno.mod`: %w", err) + } + + return mod.Draft, nil +} diff --git a/contribs/gnodev/pkg/packages/resolver.go b/contribs/gnodev/pkg/packages/resolver.go new file mode 100644 index 00000000000..9ed9269b6d8 --- /dev/null +++ b/contribs/gnodev/pkg/packages/resolver.go @@ -0,0 +1,234 @@ +package packages + +import ( + "errors" + "fmt" + "go/parser" + "go/scanner" + "go/token" + "log/slog" + "strings" + "time" +) + +var ( + ErrResolverPackageNotFound = errors.New("package not found") + ErrResolverPackageSkip = errors.New("package has been skip") +) + +type Resolver interface { + Name() string + Resolve(fset *token.FileSet, path string) (*Package, error) +} + +type NoopResolver struct{} + +func (NoopResolver) Name() string { return "" } +func (NoopResolver) Resolve(fset *token.FileSet, path string) (*Package, error) { + return nil, ErrResolverPackageNotFound +} + +// Chain Resolver + +type ChainedResolver []Resolver + +func ChainResolvers(rs ...Resolver) Resolver { + switch len(rs) { + case 0: + return &NoopResolver{} + case 1: + return rs[0] + default: + return ChainedResolver(rs) + } +} + +func (cr ChainedResolver) Name() string { + names := make([]string, 0, len(cr)) + for _, r := range cr { + rname := r.Name() + if rname == "" { + continue + } + + names = append(names, rname) + } + + return strings.Join(names, "/") +} + +func (cr ChainedResolver) Resolve(fset *token.FileSet, path string) (*Package, error) { + for _, resolver := range cr { + pkg, err := resolver.Resolve(fset, path) + if err == nil { + return pkg, nil + } else if errors.Is(err, ErrResolverPackageNotFound) { + continue + } + + return nil, fmt.Errorf("resolver %q error: %w", resolver.Name(), err) + } + + return nil, ErrResolverPackageNotFound +} + +type MiddlewareHandler func(fset *token.FileSet, path string, next Resolver) (*Package, error) + +type middlewareResolver struct { + Handler MiddlewareHandler + Next Resolver +} + +func MiddlewareResolver(r Resolver, handlers ...MiddlewareHandler) Resolver { + // Start with the final resolver + start := r + + // Wrap each handler around the previous one + for _, handler := range handlers { + start = &middlewareResolver{ + Next: start, + Handler: handler, + } + } + + return start +} + +func (r middlewareResolver) Name() string { + return r.Next.Name() +} + +func (r *middlewareResolver) Resolve(fset *token.FileSet, path string) (*Package, error) { + if r.Handler != nil { + return r.Handler(fset, path, r.Next) + } + + return r.Next.Resolve(fset, path) +} + +// LogMiddleware creates a logging middleware handler. +func LogMiddleware(logger *slog.Logger) MiddlewareHandler { + return func(fset *token.FileSet, path string, next Resolver) (*Package, error) { + start := time.Now() + pkg, err := next.Resolve(fset, path) + switch { + case err == nil: + logger.Debug("path resolved", + "resolver", next.Name(), + "path", path, + "name", pkg.Name, + "took", time.Since(start).String(), + "location", pkg.Location, + ) + case errors.Is(err, ErrResolverPackageSkip): + logger.Debug(err.Error(), + "resolver", next.Name(), + "path", path, + "took", time.Since(start).String(), + ) + + case errors.Is(err, ErrResolverPackageNotFound): + logger.Warn(err.Error(), + "resolver", next.Name(), + "path", path, + "took", time.Since(start).String()) + + default: + logger.Error(err.Error(), + "resolver", next.Name(), + "path", path, + "took", time.Since(start).String()) + } + + return pkg, err + } +} + +type ShouldCacheFunc func(pkg *Package) bool + +func CacheAll(_ *Package) bool { return true } + +// CacheMiddleware creates a caching middleware handler. +func CacheMiddleware(shouldCache ShouldCacheFunc) MiddlewareHandler { + cacheMap := make(map[string]*Package) + return func(fset *token.FileSet, path string, next Resolver) (*Package, error) { + if pkg, ok := cacheMap[path]; ok { + return pkg, nil + } + + pkg, err := next.Resolve(fset, path) + if pkg != nil && shouldCache(pkg) { + cacheMap[path] = pkg + } + + return pkg, err + } +} + +// FilterPathHandler defines the function signature for filter handlers. +type FilterPathHandler func(path string) bool + +func FilterPathMiddleware(name string, filter FilterPathHandler) MiddlewareHandler { + return func(fset *token.FileSet, path string, next Resolver) (*Package, error) { + if filter(path) { + return nil, fmt.Errorf("filter %q: %w", name, ErrResolverPackageSkip) + } + + return next.Resolve(fset, path) + } +} + +var FilterStdlibs = FilterPathMiddleware("stdlibs", isStdPath) + +func isStdPath(path string) bool { + if i := strings.IndexRune(path, '/'); i > 0 { + if j := strings.IndexRune(path[:i], '.'); j >= 0 { + return false + } + } + + return true +} + +// PackageCheckerMiddleware creates a middleware handler for post-processing syntax. +func PackageCheckerMiddleware(logger *slog.Logger) MiddlewareHandler { + return func(fset *token.FileSet, path string, next Resolver) (*Package, error) { + // First, resolve the package using the next resolver in the chain. + pkg, err := next.Resolve(fset, path) + if err != nil { + return nil, err + } + + if err := pkg.Validate(); err != nil { + return nil, fmt.Errorf("invalid package %q: %w", path, err) + } + + // Post-process each file in the package. + for _, file := range pkg.Files { + fname := file.Name + if !isGnoFile(fname) { + continue + } + + logger.Debug("checking syntax", "path", path, "filename", fname) + _, err := parser.ParseFile(fset, file.Name, file.Body, parser.AllErrors) + if err == nil { + continue + } + + if el, ok := err.(scanner.ErrorList); ok { + for _, e := range el { + logger.Error("syntax error", + "path", path, + "filename", fname, + "err", e.Error(), + ) + } + } + + return nil, fmt.Errorf("file %q have error(s)", file.Name) + } + + return pkg, nil + } +} diff --git a/contribs/gnodev/pkg/packages/resolver_local.go b/contribs/gnodev/pkg/packages/resolver_local.go new file mode 100644 index 00000000000..0312b262403 --- /dev/null +++ b/contribs/gnodev/pkg/packages/resolver_local.go @@ -0,0 +1,40 @@ +package packages + +import ( + "fmt" + "go/token" + "path" + "path/filepath" + "strings" +) + +type LocalResolver struct { + Path string + Dir string +} + +func NewLocalResolver(path, dir string) *LocalResolver { + return &LocalResolver{ + Path: path, + Dir: dir, + } +} + +func (r *LocalResolver) Name() string { + return fmt.Sprintf("local<%s>", path.Base(r.Dir)) +} + +func (r LocalResolver) IsValid() bool { + pkg, err := r.Resolve(token.NewFileSet(), r.Path) + return err == nil && pkg != nil +} + +func (r LocalResolver) Resolve(fset *token.FileSet, path string) (*Package, error) { + after, found := strings.CutPrefix(path, r.Path) + if !found { + return nil, ErrResolverPackageNotFound + } + + dir := filepath.Join(r.Dir, after) + return ReadPackageFromDir(fset, path, dir) +} diff --git a/contribs/gnodev/pkg/packages/resolver_mock.go b/contribs/gnodev/pkg/packages/resolver_mock.go new file mode 100644 index 00000000000..f6a09af8883 --- /dev/null +++ b/contribs/gnodev/pkg/packages/resolver_mock.go @@ -0,0 +1,40 @@ +package packages + +import ( + "go/token" + + "github.com/gnolang/gno/gnovm" +) + +type MockResolver struct { + pkgs map[string]*gnovm.MemPackage + resolveCalls map[string]int // Track resolve calls per path +} + +func NewMockResolver(pkgs ...*gnovm.MemPackage) *MockResolver { + mappkgs := make(map[string]*gnovm.MemPackage, len(pkgs)) + for _, pkg := range pkgs { + mappkgs[pkg.Path] = pkg + } + return &MockResolver{ + pkgs: mappkgs, + resolveCalls: make(map[string]int), + } +} + +func (m *MockResolver) ResolveCalls(fset *token.FileSet, path string) int { + count, _ := m.resolveCalls[path] + return count +} + +func (m *MockResolver) Resolve(fset *token.FileSet, path string) (*Package, error) { + m.resolveCalls[path]++ // Increment call count + if mempkg, ok := m.pkgs[path]; ok { + return &Package{MemPackage: *mempkg}, nil + } + return nil, ErrResolverPackageNotFound +} + +func (m *MockResolver) Name() string { + return "mock" +} diff --git a/contribs/gnodev/pkg/packages/resolver_remote.go b/contribs/gnodev/pkg/packages/resolver_remote.go new file mode 100644 index 00000000000..ff7b8fa723a --- /dev/null +++ b/contribs/gnodev/pkg/packages/resolver_remote.go @@ -0,0 +1,94 @@ +package packages + +import ( + "bytes" + "errors" + "fmt" + "go/parser" + "go/token" + gopath "path" + "strings" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" +) + +type remoteResolver struct { + *client.RPCClient + name string + fset *token.FileSet +} + +func NewRemoteResolver(name string, cl *client.RPCClient) Resolver { + return &remoteResolver{ + RPCClient: cl, + name: name, + fset: token.NewFileSet(), + } +} + +func (res *remoteResolver) Name() string { + return fmt.Sprintf("remote<%s>", res.name) +} + +func (res *remoteResolver) Resolve(fset *token.FileSet, path string) (*Package, error) { + const qpath = "vm/qfile" + + // First query files + data := []byte(path) + qres, err := res.RPCClient.ABCIQuery(qpath, data) + if err != nil { + return nil, fmt.Errorf("client unable to query: %w", err) + } + + if err := qres.Response.Error; err != nil { + if errors.Is(err, vm.InvalidPkgPathError{}) || + strings.HasSuffix(err.Error(), "is not available") { // XXX: find a better to check this + return nil, ErrResolverPackageNotFound + } + + return nil, fmt.Errorf("querying %q error: %w", path, err) + } + + var name string + memFiles := []*gnovm.MemFile{} + files := bytes.Split(qres.Response.Data, []byte{'\n'}) + for _, filename := range files { + fname := string(filename) + fpath := gopath.Join(path, fname) + qres, err := res.RPCClient.ABCIQuery(qpath, []byte(fpath)) + if err != nil { + return nil, fmt.Errorf("unable to query path") + } + + if err := qres.Response.Error; err != nil { + return nil, fmt.Errorf("unable to query file %q on path %q: %w", fname, path, err) + } + body := qres.Response.Data + + // Check package name + if name == "" && isGnoFile(fname) && !isTestFile(fname) { + // Check package name + f, err := parser.ParseFile(fset, fname, body, parser.PackageClauseOnly) + if err != nil { + return nil, fmt.Errorf("unable to parse file %q: %w", fname, err) + } + name = f.Name.Name + } + + memFiles = append(memFiles, &gnovm.MemFile{ + Name: fname, Body: string(body), + }) + } + + return &Package{ + MemPackage: gnovm.MemPackage{ + Name: name, + Path: path, + Files: memFiles, + }, + Kind: PackageKindRemote, + Location: path, + }, nil +} diff --git a/contribs/gnodev/pkg/packages/resolver_remote_test.go b/contribs/gnodev/pkg/packages/resolver_remote_test.go new file mode 100644 index 00000000000..69347c0ad4d --- /dev/null +++ b/contribs/gnodev/pkg/packages/resolver_remote_test.go @@ -0,0 +1 @@ +package packages diff --git a/contribs/gnodev/pkg/packages/resolver_root.go b/contribs/gnodev/pkg/packages/resolver_root.go new file mode 100644 index 00000000000..ae6a9d416ea --- /dev/null +++ b/contribs/gnodev/pkg/packages/resolver_root.go @@ -0,0 +1,30 @@ +package packages + +import ( + "fmt" + "go/token" + "os" + "path/filepath" +) + +type rootResolver struct { + root string // Root folder +} + +func NewRootResolver(path string) Resolver { + return &rootResolver{root: path} +} + +func (r *rootResolver) Name() string { + return fmt.Sprintf("root<%s>", filepath.Base(r.root)) +} + +func (r *rootResolver) Resolve(fset *token.FileSet, path string) (*Package, error) { + dir := filepath.Join(r.root, path) + _, err := os.Stat(dir) + if err != nil { + return nil, ErrResolverPackageNotFound + } + + return ReadPackageFromDir(fset, path, dir) +} diff --git a/contribs/gnodev/pkg/packages/resolver_test.go b/contribs/gnodev/pkg/packages/resolver_test.go new file mode 100644 index 00000000000..9341bb80d7b --- /dev/null +++ b/contribs/gnodev/pkg/packages/resolver_test.go @@ -0,0 +1,290 @@ +package packages + +import ( + "bytes" + "errors" + "go/token" + "log/slog" + "path/filepath" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLogMiddleware(t *testing.T) { + t.Parallel() + + mockResolver := NewMockResolver(&gnovm.MemPackage{ + Path: "abc.xy/test/pkg", + Name: "pkg", + Files: []*gnovm.MemFile{ + {Name: "file.gno", Body: "package pkg"}, + }, + }) + + t.Run("logs package not found", func(t *testing.T) { + t.Parallel() + + var buff bytes.Buffer + + logger := slog.New(slog.NewTextHandler(&buff, &slog.HandlerOptions{})) + middleware := LogMiddleware(logger) + + resolver := MiddlewareResolver(mockResolver, middleware) + pkg, err := resolver.Resolve(token.NewFileSet(), "abc.xy/invalid/pkg") + require.Error(t, err) + require.Nil(t, pkg) + assert.Contains(t, buff.String(), "package not found") + }) + + t.Run("logs package resolution", func(t *testing.T) { + t.Parallel() + + var buff bytes.Buffer + logger := slog.New(slog.NewTextHandler(&buff, &slog.HandlerOptions{ + Level: slog.LevelDebug, + })) + middleware := LogMiddleware(logger) + + resolver := MiddlewareResolver(mockResolver, middleware) + pkg, err := resolver.Resolve(token.NewFileSet(), "abc.xy/test/pkg") + require.NoError(t, err) + require.NotNil(t, pkg) + assert.Contains(t, buff.String(), "path resolved") + }) +} + +func TestCacheMiddleware(t *testing.T) { + t.Parallel() + + pkg := &gnovm.MemPackage{Path: "abc.xy/cached/pkg", Name: "pkg"} + t.Run("caches resolved packages", func(t *testing.T) { + t.Parallel() + + mockResolver := NewMockResolver(pkg) + cacheMiddleware := CacheMiddleware(CacheAll) + cachedResolver := MiddlewareResolver(mockResolver, cacheMiddleware) + + // First call + pkg1, err := cachedResolver.Resolve(token.NewFileSet(), pkg.Path) + require.NoError(t, err) + require.Equal(t, 1, mockResolver.resolveCalls[pkg.Path]) + + // Second call + pkg2, err := cachedResolver.Resolve(token.NewFileSet(), pkg.Path) + require.NoError(t, err) + require.Same(t, pkg1, pkg2) + require.Equal(t, 1, mockResolver.resolveCalls[pkg.Path]) + }) + + t.Run("no cache when shouldCache is false", func(t *testing.T) { + t.Parallel() + + mockResolver := NewMockResolver(pkg) + cacheMiddleware := CacheMiddleware(func(*Package) bool { return false }) + cachedResolver := MiddlewareResolver(mockResolver, cacheMiddleware) + + pkg1, err := cachedResolver.Resolve(token.NewFileSet(), pkg.Path) + require.NoError(t, err) + pkg2, err := cachedResolver.Resolve(token.NewFileSet(), pkg.Path) + require.NoError(t, err) + require.NotSame(t, pkg1, pkg2) + require.Equal(t, 2, mockResolver.resolveCalls[pkg.Path]) + }) +} + +func TestFilterStdlibsMiddleware(t *testing.T) { + t.Parallel() + + middleware := FilterStdlibs + mockResolver := NewMockResolver(&gnovm.MemPackage{ + Path: "abc.xy/pkg", + Name: "pkg", + Files: []*gnovm.MemFile{ + {Name: "file.gno", Body: "package pkg"}, + }, + }) + filteredResolver := MiddlewareResolver(mockResolver, middleware) + + t.Run("filters stdlib paths", func(t *testing.T) { + t.Parallel() + + _, err := filteredResolver.Resolve(token.NewFileSet(), "fmt") + require.Error(t, err) + require.True(t, errors.Is(err, ErrResolverPackageSkip)) + require.Equal(t, 0, mockResolver.resolveCalls["fmt"]) + }) + + t.Run("allows non-stdlib paths", func(t *testing.T) { + t.Parallel() + + pkg, err := filteredResolver.Resolve(token.NewFileSet(), "abc.xy/pkg") + require.NoError(t, err) + require.NotNil(t, pkg) + require.Equal(t, 1, mockResolver.resolveCalls["abc.xy/pkg"]) + }) +} + +func TestPackageCheckerMiddleware(t *testing.T) { + t.Parallel() + + logger := log.NewTestingLogger(t) + t.Run("valid package syntax", func(t *testing.T) { + t.Parallel() + + validPkg := &gnovm.MemPackage{ + Path: "abc.xy/r/valid/pkg", + Name: "valid", + Files: []*gnovm.MemFile{ + {Name: "valid.gno", Body: "package valid; func Foo() {}"}, + }, + } + mockResolver := NewMockResolver(validPkg) + middleware := PackageCheckerMiddleware(logger) + resolver := MiddlewareResolver(mockResolver, middleware) + + pkg, err := resolver.Resolve(token.NewFileSet(), validPkg.Path) + require.NoError(t, err) + require.NotNil(t, pkg) + }) + + t.Run("invalid package syntax", func(t *testing.T) { + t.Parallel() + + invalidPkg := &gnovm.MemPackage{ + Path: "abc.xy/r/invalid/pkg", + Name: "invalid", + Files: []*gnovm.MemFile{ + {Name: "invalid.gno", Body: "package invalid\nfunc Foo() {"}, + }, + } + mockResolver := NewMockResolver(invalidPkg) + middleware := PackageCheckerMiddleware(logger) + resolver := MiddlewareResolver(mockResolver, middleware) + + _, err := resolver.Resolve(token.NewFileSet(), invalidPkg.Path) + require.Error(t, err) + require.Contains(t, err.Error(), `file "invalid.gno" have error(s)`) + }) + + t.Run("ignores non-gno files", func(t *testing.T) { + t.Parallel() + + nonGnoPkg := &gnovm.MemPackage{ + Path: "abc.xy/r/non/gno/pkg", + Name: "pkg", + Files: []*gnovm.MemFile{ + {Name: "README.md", Body: "# Documentation"}, + }, + } + mockResolver := NewMockResolver(nonGnoPkg) + middleware := PackageCheckerMiddleware(logger) + resolver := MiddlewareResolver(mockResolver, middleware) + + _, err := resolver.Resolve(token.NewFileSet(), nonGnoPkg.Path) + require.NoError(t, err) + }) +} + +func TestResolverLocal_Resolve(t *testing.T) { + t.Parallel() + + const anotherPath = "abc.xy/another/path" + localResolver := NewLocalResolver(anotherPath, filepath.Join("./testdata", TestdataPkgA)) + + t.Run("valid package", func(t *testing.T) { + t.Parallel() + + pkg, err := localResolver.Resolve(token.NewFileSet(), anotherPath) + require.NoError(t, err) + require.NotNil(t, pkg) + require.Equal(t, pkg.Name, "aa") + }) + + t.Run("invalid package", func(t *testing.T) { + t.Parallel() + + pkg, err := localResolver.Resolve(token.NewFileSet(), "abc.xy/wrong/package") + require.Nil(t, pkg) + require.Error(t, err) + require.ErrorIs(t, err, ErrResolverPackageNotFound) + }) +} + +func TestResolver_ResolveRemote(t *testing.T) { + const targetPath = "gno.land/r/target/path" + + mempkg := gnovm.MemPackage{ + Name: "foo", + Path: targetPath, + Files: []*gnovm.MemFile{ + { + Name: "foo.gno", + Body: `package foo; func Render(_ string) string { return "bar" }`, + }, + {Name: "gno.mod", Body: `module ` + targetPath}, + }, + } + + rootdir := gnoenv.RootDir() + cfg := integration.TestingMinimalNodeConfig(rootdir) + logger := log.NewTestingLogger(t) + + // Setup genesis state + privKey := secp256k1.GenPrivKey() + cfg.Genesis.AppState = integration.GenerateTestingGenesisState(privKey, mempkg) + + _, address := integration.TestingInMemoryNode(t, logger, cfg) + cl, err := client.NewHTTPClient(address) + require.NoError(t, err) + + remoteResolver := NewRemoteResolver(address, cl) + t.Run("valid package", func(t *testing.T) { + pkg, err := remoteResolver.Resolve(token.NewFileSet(), mempkg.Path) + require.NoError(t, err) + require.NotNil(t, pkg) + assert.Equal(t, mempkg, pkg.MemPackage) + }) + + t.Run("invalid package", func(t *testing.T) { + pkg, err := remoteResolver.Resolve(token.NewFileSet(), "gno.land/r/not/a/valid/package") + require.Nil(t, pkg) + require.Error(t, err) + require.ErrorIs(t, err, ErrResolverPackageNotFound) + }) +} + +func TestResolverRoot_Resolve(t *testing.T) { + t.Parallel() + + fsResolver := NewRootResolver("./testdata") + t.Run("valid packages", func(t *testing.T) { + t.Parallel() + + for _, tpkg := range []string{TestdataPkgA, TestdataPkgB, TestdataPkgC} { + t.Run(tpkg, func(t *testing.T) { + pkg, err := fsResolver.Resolve(token.NewFileSet(), tpkg) + require.NoError(t, err) + require.NotNil(t, pkg) + require.Equal(t, tpkg, pkg.Path) + require.Equal(t, filepath.Base(tpkg), pkg.Name) + }) + } + }) + + t.Run("invalid packages", func(t *testing.T) { + t.Parallel() + + pkg, err := fsResolver.Resolve(token.NewFileSet(), "abc.xy/wrong/package") + require.Nil(t, pkg) + require.Error(t, err) + require.ErrorIs(t, err, ErrResolverPackageNotFound) + }) +} diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/aa/file.gno b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/aa/file.gno new file mode 100644 index 00000000000..14492ef76f3 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/aa/file.gno @@ -0,0 +1 @@ +package aa diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/aa/gno.mod b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/aa/gno.mod new file mode 100644 index 00000000000..071e676d43e --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/aa/gno.mod @@ -0,0 +1 @@ +module abc.xy/nested/aa diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/bb/file.gno b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/bb/file.gno new file mode 100644 index 00000000000..592f1946da0 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/bb/file.gno @@ -0,0 +1 @@ +package bb diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/bb/gno.mod b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/bb/gno.mod new file mode 100644 index 00000000000..2e0f55a7954 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/bb/gno.mod @@ -0,0 +1 @@ +module abc.xy/nested/nested/bb \ No newline at end of file diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/cc/file.gno b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/cc/file.gno new file mode 100644 index 00000000000..10702f6990c --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/cc/file.gno @@ -0,0 +1 @@ +package cc diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/cc/gno.mod b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/cc/gno.mod new file mode 100644 index 00000000000..0932deb1366 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/nested/nested/cc/gno.mod @@ -0,0 +1 @@ +module abc.xy/nested/nested/cc \ No newline at end of file diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/aa/file.gno b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/aa/file.gno new file mode 100644 index 00000000000..b809785a376 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/aa/file.gno @@ -0,0 +1,3 @@ +package aa + +type SA struct{} diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/aa/gno.mod b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/aa/gno.mod new file mode 100644 index 00000000000..02d58054ca6 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/aa/gno.mod @@ -0,0 +1 @@ +module abc.xy/pkg/aa \ No newline at end of file diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/bb/file.gno b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/bb/file.gno new file mode 100644 index 00000000000..5cca9ec3c21 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/bb/file.gno @@ -0,0 +1,5 @@ +package bb + +import "abc.xy/pkg/aa" + +type SB = aa.SA diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/bb/gno.mod b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/bb/gno.mod new file mode 100644 index 00000000000..b5d760d6f75 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/bb/gno.mod @@ -0,0 +1 @@ +module abc.xy/pkg/bb \ No newline at end of file diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/cc/file.gno b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/cc/file.gno new file mode 100644 index 00000000000..21819a7b686 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/cc/file.gno @@ -0,0 +1,5 @@ +package cc + +import "abc.xy/pkg/bb" + +type SC = bb.SB diff --git a/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/cc/gno.mod b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/cc/gno.mod new file mode 100644 index 00000000000..bc993583fd3 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata/abc.xy/pkg/cc/gno.mod @@ -0,0 +1 @@ +module abc.xy/pkg/cc \ No newline at end of file diff --git a/contribs/gnodev/pkg/packages/testdata_test.go b/contribs/gnodev/pkg/packages/testdata_test.go new file mode 100644 index 00000000000..5c9a8b45cd5 --- /dev/null +++ b/contribs/gnodev/pkg/packages/testdata_test.go @@ -0,0 +1,44 @@ +// This test file serves as a reference for the testdata directory tree. + +package packages + +// The structure of the testdata directory is as follows: +// +// testdata +// ├── abc.xy +// ├── nested +// │ ├── aa +// │ │ └── gno.mod +// │ └── nested +// │ ├── bb +// │ │ └── gno.mod +// │ └── cc +// │ └── gno.mod +// └── pkg +// ├── aa +// │ ├── file1.gno +// │ └── gno.mod +// ├── bb // depends on aa +// │ ├── file1.gno +// │ └── gno.mod +// └── cc // depends on bb +// ├── file1.gno +// └── gno.mod + +const ( + TestdataPkgA = "abc.xy/pkg/aa" + TestdataPkgB = "abc.xy/pkg/bb" + TestdataPkgC = "abc.xy/pkg/cc" +) + +// List of testdata package paths +var testdataPkgs = []string{TestdataPkgA, TestdataPkgB, TestdataPkgC} + +const ( + TestdataNestedA = "abc.xy/nested/aa" // Path to nested package A + TestdataNestedB = "abc.xy/nested/nested/bb" // Path to nested package B + TestdataNestedC = "abc.xy/nested/nested/cc" // Path to nested package C +) + +// List of nested package paths +var testdataNested = []string{TestdataNestedA, TestdataNestedB, TestdataNestedC} diff --git a/contribs/gnodev/pkg/packages/utils.go b/contribs/gnodev/pkg/packages/utils.go new file mode 100644 index 00000000000..93160a3a1a5 --- /dev/null +++ b/contribs/gnodev/pkg/packages/utils.go @@ -0,0 +1,14 @@ +package packages + +import ( + "path/filepath" + "strings" +) + +func isGnoFile(name string) bool { + return filepath.Ext(name) == ".gno" && !strings.HasPrefix(name, ".") +} + +func isTestFile(name string) bool { + return strings.HasSuffix(name, "_filetest.gno") || strings.HasSuffix(name, "_test.gno") +} diff --git a/contribs/gnodev/pkg/packages/utils_other.go b/contribs/gnodev/pkg/packages/utils_other.go new file mode 100644 index 00000000000..0455aa57480 --- /dev/null +++ b/contribs/gnodev/pkg/packages/utils_other.go @@ -0,0 +1,9 @@ +//go:build !windows +// +build !windows + +package packages + +// NormalizeFilepathToPath normalize path an unix like path +func NormalizeFilepathToPath(path string) string { + return path +} diff --git a/contribs/gnodev/pkg/packages/utils_windows.go b/contribs/gnodev/pkg/packages/utils_windows.go new file mode 100644 index 00000000000..23c7fa54379 --- /dev/null +++ b/contribs/gnodev/pkg/packages/utils_windows.go @@ -0,0 +1,11 @@ +//go:build windows +// +build windows + +package packages + +import "strings" + +// NormalizeFilepathToPath normalize path an unix like path +func NormalizeFilepathToPath(path string) string { + return strings.ReplaceAll(path, "\\", "/") +} diff --git a/contribs/gnodev/pkg/proxy/path_interceptor.go b/contribs/gnodev/pkg/proxy/path_interceptor.go new file mode 100644 index 00000000000..88240d904a5 --- /dev/null +++ b/contribs/gnodev/pkg/proxy/path_interceptor.go @@ -0,0 +1,337 @@ +package proxy + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net" + "net/http" + gopath "path" + "strings" + "sync" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/amino" + rpctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" + "github.com/gnolang/gno/tm2/pkg/std" +) + +type PathHandler func(path ...string) + +type PathInterceptor struct { + proxyAddr, targetAddr net.Addr + + logger *slog.Logger + listener net.Listener + handlers []PathHandler + muHandlers sync.RWMutex +} + +// NewPathInterceptor creates a new path proxy interceptor. +func NewPathInterceptor(logger *slog.Logger, target net.Addr) (*PathInterceptor, error) { + // Create a listener on the target address + proxyListener, err := net.Listen(target.Network(), target.String()) + if err != nil { + return nil, fmt.Errorf("failed to listen on %s://%s", target.Network(), target.String()) + } + + // Find on a new random port for the target + targetListener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, fmt.Errorf("failed to listen on tcp://127.0.0.1:0") + } + proxyAddr := targetListener.Addr() + // Immediately close this listener after proxy initialization + defer targetListener.Close() + + proxy := &PathInterceptor{ + listener: proxyListener, + logger: logger, + targetAddr: target, + proxyAddr: proxyAddr, + } + + go proxy.handleConnections() + + return proxy, nil +} + +// HandlePath adds a new path handler to the interceptor. +func (proxy *PathInterceptor) HandlePath(fn PathHandler) { + proxy.muHandlers.Lock() + defer proxy.muHandlers.Unlock() + proxy.handlers = append(proxy.handlers, fn) +} + +// ProxyAddress returns the network address of the proxy. +func (proxy *PathInterceptor) ProxyAddress() string { + return fmt.Sprintf("%s://%s", proxy.proxyAddr.Network(), proxy.proxyAddr.String()) +} + +// TargetAddress returns the network address of the target. +func (proxy *PathInterceptor) TargetAddress() string { + return fmt.Sprintf("%s://%s", proxy.targetAddr.Network(), proxy.targetAddr.String()) +} + +// handleConnections manages incoming connections to the proxy. +func (proxy *PathInterceptor) handleConnections() { + defer proxy.listener.Close() + + for { + conn, err := proxy.listener.Accept() + if err != nil { + if !errors.Is(err, net.ErrClosed) { + proxy.logger.Debug("failed to accept connection", "error", err) + } + + return + } + + proxy.logger.Debug("new connection", "remote", conn.RemoteAddr()) + go proxy.handleConnection(conn) + } +} + +// handleConnection processes a single connection between client and target. +func (proxy *PathInterceptor) handleConnection(inConn net.Conn) { + logger := proxy.logger.With(slog.String("in", inConn.RemoteAddr().String())) + + // Establish a connection to the target + outConn, err := net.Dial(proxy.proxyAddr.Network(), proxy.proxyAddr.String()) + if err != nil { + logger.Error("target connection failed", "target", proxy.proxyAddr.String(), "error", err) + inConn.Close() + return + } + logger = logger.With(slog.String("out", outConn.RemoteAddr().String())) + + // Coordinate connection closure + var closeOnce sync.Once + closeConnections := func() { + inConn.Close() + outConn.Close() + } + + // Setup bidirectional copying + var wg sync.WaitGroup + wg.Add(2) + + // Response path (target -> client) + go func() { + defer wg.Done() + defer closeOnce.Do(closeConnections) + + _, err := io.Copy(inConn, outConn) + if err == nil || errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF) { + return // Connection has been closed + } + + logger.Debug("response copy error", "error", err) + }() + + // Request path (client -> target) + go func() { + defer wg.Done() + defer closeOnce.Do(closeConnections) + + var buffer bytes.Buffer + tee := io.TeeReader(inConn, &buffer) + reader := bufio.NewReader(tee) + + // Process HTTP requests + if err := proxy.processHTTPRequests(reader, &buffer, outConn); err != nil { + if errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF) { + return // Connection has been closed + } + + if _, isNetError := err.(net.Error); isNetError { + logger.Debug("request processing error", "error", err) + return + } + + // Continue processing the connection if not a network error + } + + // Forward remaining data after HTTP processing + if buffer.Len() > 0 { + if _, err := outConn.Write(buffer.Bytes()); err != nil { + logger.Debug("buffer flush failed", "error", err) + } + } + + // Directly pipe remaining traffic + if _, err := io.Copy(outConn, inConn); err != nil && !errors.Is(err, net.ErrClosed) { + logger.Debug("raw copy failed", "error", err) + } + }() + + wg.Wait() + logger.Debug("connection closed") +} + +// processHTTPRequests handles the HTTP request/response cycle. +func (proxy *PathInterceptor) processHTTPRequests(reader *bufio.Reader, buffer *bytes.Buffer, outConn net.Conn) error { + for { + request, err := http.ReadRequest(reader) + if err != nil { + return fmt.Errorf("read request failed: %w", err) + } + + // Check for websocket upgrade + if isWebSocket(request) { + return errors.New("websocket upgrade requested") + } + + // Read and process the request body + body, err := io.ReadAll(request.Body) + request.Body.Close() + if err != nil { + return fmt.Errorf("body read failed: %w", err) + } + + if err := proxy.handleRequest(body); err != nil { + proxy.logger.Debug("request handler warning", "error", err) + } + + // Forward the original request bytes + if _, err := outConn.Write(buffer.Bytes()); err != nil { + return fmt.Errorf("request forward failed: %w", err) + } + + buffer.Reset() // Prepare for the next request + } +} + +func isWebSocket(req *http.Request) bool { + return strings.EqualFold(req.Header.Get("Upgrade"), "websocket") +} + +type uniqPaths map[string]struct{} + +func (upaths uniqPaths) list() []string { + paths := make([]string, 0, len(upaths)) + for p := range upaths { + paths = append(paths, p) + } + return paths +} + +// Add a path to +func (upaths uniqPaths) add(path string) { + path = cleanupPath(path) + upaths[path] = struct{}{} +} + +// handleRequest parses and processes the RPC request body. +func (proxy *PathInterceptor) handleRequest(body []byte) error { + ps := make(uniqPaths) + if err := parseRPCRequest(body, ps); err != nil { + return fmt.Errorf("unable to parse RPC request: %w", err) + } + + paths := ps.list() + if len(paths) == 0 { + return nil + } + + proxy.logger.Debug("parsed request paths", "paths", paths) + + proxy.muHandlers.RLock() + defer proxy.muHandlers.RUnlock() + + for _, handle := range proxy.handlers { + handle(paths...) + } + + return nil +} + +// Close closes the proxy listener. +func (proxy *PathInterceptor) Close() error { + return proxy.listener.Close() +} + +// parseRPCRequest unmarshals and processes RPC requests, returning paths. +func parseRPCRequest(body []byte, upaths uniqPaths) error { + var req rpctypes.RPCRequest + if err := json.Unmarshal(body, &req); err != nil { + return fmt.Errorf("unable to unmarshal RPC request: %w", err) + } + + switch req.Method { + case "abci_query": + var squery struct { + Path string `json:"path"` + Data []byte `json:"data,omitempty"` + } + if err := json.Unmarshal(req.Params, &squery); err != nil { + return fmt.Errorf("unable to unmarshal params: %w", err) + } + + return handleQuery(squery.Path, squery.Data, upaths) + + case "broadcast_tx_commit": + var stx struct { + Tx []byte `json:"tx"` + } + if err := json.Unmarshal(req.Params, &stx); err != nil { + return fmt.Errorf("unable to unmarshal params: %w", err) + } + + return handleTx(stx.Tx, upaths) + } + + return fmt.Errorf("unhandled method: %q", req.Method) +} + +// handleTx processes the transaction and returns relevant paths. +func handleTx(bz []byte, upaths uniqPaths) error { + var tx std.Tx + if err := amino.Unmarshal(bz, &tx); err != nil { + return fmt.Errorf("unable to unmarshal tx: %w", err) + } + + for _, msg := range tx.Msgs { + switch msg := msg.(type) { + case vm.MsgAddPackage: // MsgAddPackage should not be handled + case vm.MsgCall: + upaths.add(msg.PkgPath) + case vm.MsgRun: + upaths.add(msg.Package.Path) + } + } + + return nil +} + +// handleQuery processes the query and returns relevant paths. +func handleQuery(path string, data []byte, upaths uniqPaths) error { + switch path { + case ".app/simulate": + return handleTx(data, upaths) + + case "vm/qrender", "vm/qfile", "vm/qfuncs", "vm/qeval": + path, _, _ := strings.Cut(string(data), ":") // Cut arguments out + upaths.add(path) + return nil + + default: + return fmt.Errorf("unhandled: %q", path) + } + + // XXX: handle more cases +} + +func cleanupPath(path string) string { + path = gopath.Clean(path) + // If path is a file, grab the directory instead + if ext := gopath.Ext(path); ext != "" { + path = gopath.Dir(path) + } + + return path +} diff --git a/contribs/gnodev/pkg/proxy/path_interceptor_test.go b/contribs/gnodev/pkg/proxy/path_interceptor_test.go new file mode 100644 index 00000000000..0e1e95e82a4 --- /dev/null +++ b/contribs/gnodev/pkg/proxy/path_interceptor_test.go @@ -0,0 +1,180 @@ +package proxy_test + +import ( + "net" + "net/http" + "path" + "path/filepath" + "testing" + + "github.com/gnolang/gno/contribs/gnodev/pkg/proxy" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestProxy(t *testing.T) { + const targetPath = "gno.land/r/target/foo" + + pkg := gnovm.MemPackage{ + Name: "foo", + Path: targetPath, + Files: []*gnovm.MemFile{ + { + Name: "foo.gno", + Body: `package foo; func Render(_ string) string { return "foo" }`, + }, + {Name: "gno.mod", Body: `module ` + targetPath}, + }, + } + + rootdir := gnoenv.RootDir() + cfg := integration.TestingMinimalNodeConfig(rootdir) + logger := log.NewTestingLogger(t) + + tmp := t.TempDir() + sock := filepath.Join(tmp, "node.sock") + addr, err := net.ResolveUnixAddr("unix", sock) + require.NoError(t, err) + + // Create proxy + interceptor, err := proxy.NewPathInterceptor(logger, addr) + require.NoError(t, err) + defer interceptor.Close() + cfg.TMConfig.RPC.ListenAddress = interceptor.ProxyAddress() + cfg.SkipGenesisVerification = true + + // Setup genesis + privKey := secp256k1.GenPrivKey() + cfg.Genesis.AppState = integration.GenerateTestingGenesisState(privKey, pkg) + creator := privKey.PubKey().Address() + + integration.TestingInMemoryNode(t, logger, cfg) + pathChan := make(chan []string, 1) + interceptor.HandlePath(func(paths ...string) { + pathChan <- paths + }) + + // ---- Test Cases ---- + + t.Run("valid_vm_query", func(t *testing.T) { + cli, err := client.NewHTTPClient(interceptor.TargetAddress()) + require.NoError(t, err) + + res, err := cli.ABCIQuery("vm/qrender", []byte(targetPath+":\n")) + require.NoError(t, err) + assert.Nil(t, res.Response.Error) + + select { + case paths := <-pathChan: + require.Len(t, paths, 1) + assert.Equal(t, []string{targetPath}, paths) + default: + t.Fatal("paths not captured") + } + }) + + t.Run("valid_vm_query_file", func(t *testing.T) { + cli, err := client.NewHTTPClient(interceptor.TargetAddress()) + require.NoError(t, err) + + res, err := cli.ABCIQuery("vm/qfile", []byte(path.Join(targetPath, "foo.gno"))) + require.NoError(t, err) + assert.Nil(t, res.Response.Error) + + select { + case paths := <-pathChan: + require.Len(t, paths, 1) + assert.Equal(t, []string{targetPath}, paths) + default: + t.Fatal("paths not captured") + } + }) + + t.Run("simulate_tx_paths", func(t *testing.T) { + // Build transaction with multiple messages + var tx std.Tx + send := std.MustParseCoins(ugnot.ValueString(10_000_000)) + tx.Fee = std.Fee{GasWanted: 1e6, GasFee: std.Coin{Amount: 1e6, Denom: "ugnot"}} + tx.Msgs = []std.Msg{ + vm.NewMsgCall(creator, send, targetPath, "Render", []string{""}), + vm.NewMsgCall(creator, send, targetPath, "Render", []string{""}), + vm.NewMsgCall(creator, send, targetPath, "Render", []string{""}), + } + + bytes, err := tx.GetSignBytes(cfg.Genesis.ChainID, 0, 0) + require.NoError(t, err) + signature, err := privKey.Sign(bytes) + require.NoError(t, err) + tx.Signatures = []std.Signature{{PubKey: privKey.PubKey(), Signature: signature}} + + bz, err := amino.Marshal(tx) + require.NoError(t, err) + + cli, err := client.NewHTTPClient(interceptor.TargetAddress()) + require.NoError(t, err) + + res, err := cli.BroadcastTxCommit(bz) + require.NoError(t, err) + assert.NoError(t, res.CheckTx.Error) + assert.NoError(t, res.DeliverTx.Error) + + select { + case paths := <-pathChan: + require.Len(t, paths, 1) + assert.Equal(t, []string{targetPath}, paths) + default: + t.Fatal("paths not captured") + } + }) + + t.Run("websocket_forward", func(t *testing.T) { + // For now simply try to connect and upgrade the connection + // XXX: fully support ws + + conn, err := net.Dial(addr.Network(), addr.String()) + require.NoError(t, err) + defer conn.Close() + + // Send WebSocket handshake + req, _ := http.NewRequest("GET", "http://"+interceptor.TargetAddress(), nil) + req.Header.Set("Upgrade", "websocket") + req.Header.Set("Connection", "Upgrade") + err = req.Write(conn) + require.NoError(t, err) + }) + + t.Run("invalid_query_data", func(t *testing.T) { + // Making a valid call but not supported by the proxy + // should succeed + query := "auth/accounts/" + creator.String() + + cli, err := client.NewHTTPClient(interceptor.TargetAddress()) + require.NoError(t, err) + defer cli.Close() + + res, err := cli.ABCIQuery(query, []byte{}) + require.NoError(t, err) + require.NoError(t, res.Response.Error) + + var qret struct{ BaseAccount std.BaseAccount } + err = amino.UnmarshalJSON(res.Response.Data, &qret) + require.NoError(t, err) + assert.Equal(t, qret.BaseAccount.Address, creator) + + select { + case paths := <-pathChan: + require.FailNowf(t, "should not catch a path", "catched: %+v", paths) + default: + } + }) +} diff --git a/contribs/gnodev/pkg/rawterm/keypress.go b/contribs/gnodev/pkg/rawterm/keypress.go index 45c64c999dd..e9c1728bd4b 100644 --- a/contribs/gnodev/pkg/rawterm/keypress.go +++ b/contribs/gnodev/pkg/rawterm/keypress.go @@ -26,6 +26,12 @@ const ( KeyN KeyPress = 'N' KeyP KeyPress = 'P' KeyR KeyPress = 'R' + + // Special keys + KeyUp KeyPress = 0x80 // Arbitrary value outside ASCII range + KeyDown KeyPress = 0x81 + KeyLeft KeyPress = 0x82 + KeyRight KeyPress = 0x83 ) func (k KeyPress) Upper() KeyPress { @@ -52,6 +58,14 @@ func (k KeyPress) String() string { return "Ctrl+S" case KeyCtrlT: return "Ctrl+T" + case KeyUp: + return "Up Arrow" + case KeyDown: + return "Down Arrow" + case KeyLeft: + return "Left Arrow" + case KeyRight: + return "Right Arrow" default: // For printable ASCII characters if k > 0x20 && k < 0x7e { diff --git a/contribs/gnodev/pkg/rawterm/rawterm.go b/contribs/gnodev/pkg/rawterm/rawterm.go index 58b8dde1530..7ff4cadaf94 100644 --- a/contribs/gnodev/pkg/rawterm/rawterm.go +++ b/contribs/gnodev/pkg/rawterm/rawterm.go @@ -54,12 +54,31 @@ func (rt *RawTerm) read(buf []byte) (n int, err error) { } func (rt *RawTerm) ReadKeyPress() (KeyPress, error) { - buf := make([]byte, 1) - if _, err := rt.read(buf); err != nil { + buf := make([]byte, 3) + n, err := rt.read(buf) + if err != nil { return KeyNone, err } - return KeyPress(buf[0]), nil + if n == 1 && buf[0] != '\x1b' { + // Single character, not an escape sequence + return KeyPress(buf[0]), nil + } + + if n >= 3 && buf[0] == '\x1b' && buf[1] == '[' { + switch buf[2] { + case 'A': + return KeyUp, nil + case 'B': + return KeyDown, nil + case 'C': + return KeyRight, nil + case 'D': + return KeyLeft, nil + } + } + + return KeyNone, fmt.Errorf("unknown key sequence: %v", buf[:n]) } // writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n. diff --git a/contribs/gnodev/pkg/watcher/watch.go b/contribs/gnodev/pkg/watcher/watch.go index 63158a06c4b..0a7d284bd37 100644 --- a/contribs/gnodev/pkg/watcher/watch.go +++ b/contribs/gnodev/pkg/watcher/watch.go @@ -5,15 +5,14 @@ import ( "fmt" "log/slog" "path/filepath" - "sort" "strings" "time" emitter "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" events "github.com/gnolang/gno/contribs/gnodev/pkg/events" + "github.com/gnolang/gno/contribs/gnodev/pkg/packages" "github.com/fsnotify/fsnotify" - "github.com/gnolang/gno/gnovm/pkg/gnomod" ) type PackageWatcher struct { @@ -25,7 +24,6 @@ type PackageWatcher struct { logger *slog.Logger watcher *fsnotify.Watcher - pkgsDir []string emitter emitter.Emitter } @@ -39,7 +37,6 @@ func NewPackageWatcher(logger *slog.Logger, emitter emitter.Emitter) (*PackageWa p := &PackageWatcher{ ctx: ctx, stop: cancel, - pkgsDir: []string{}, logger: logger, watcher: watcher, emitter: emitter, @@ -61,7 +58,7 @@ func (p *PackageWatcher) startWatching() { defer close(pkgsUpdateChan) var debounceTimer <-chan time.Time - pathList := []string{} + filesList := []string{} var err error for err == nil { @@ -72,10 +69,10 @@ func (p *PackageWatcher) startWatching() { err = fmt.Errorf("watch error: %w", watchErr) case <-debounceTimer: // Process and emit package updates after the debounce interval - updates := p.generatePackagesUpdateList(pathList) + updates := p.generatePackagesUpdateList(filesList) for _, update := range updates { p.logger.Info("packages update", - "pkg", update.Package, + "pkg", update.PackageDir, "files", update.Files, ) } @@ -87,7 +84,7 @@ func (p *PackageWatcher) startWatching() { }) // Reset the path list and debounce timer - pathList = []string{} + filesList = []string{} debounceTimer = nil case evt := <-p.watcher.Events: // Only handle write operations @@ -95,7 +92,7 @@ func (p *PackageWatcher) startWatching() { continue } - pathList = append(pathList, evt.Name) + filesList = append(filesList, evt.Name) // Set up the debounce timer debounceTimer = time.After(timeout) @@ -114,69 +111,72 @@ func (p *PackageWatcher) Stop() { p.stop() } -// AddPackages adds new packages to the watcher. -// Packages are sorted by their length in descending order to facilitate easier -// and more efficient matching with corresponding paths. The longest paths are -// compared first. -func (p *PackageWatcher) AddPackages(pkgs ...gnomod.Pkg) error { +func (p *PackageWatcher) UpdatePackagesWatch(pkgs ...packages.Package) { + watchList := p.watcher.WatchList() + + oldPkgs := make(map[string]struct{}, len(watchList)) + for _, path := range watchList { + oldPkgs[path] = struct{}{} + } + + newPkgs := make(map[string]struct{}, len(pkgs)) for _, pkg := range pkgs { - dir := pkg.Dir + if pkg.Kind != packages.PackageKindFS { + continue + } - abs, err := filepath.Abs(dir) + dir, err := filepath.Abs(pkg.Location) if err != nil { - return fmt.Errorf("unable to get absolute path of %q: %w", dir, err) + p.logger.Error("Unable to get absolute path", "path", pkg.Location, "error", err) + continue } - // Use binary search to find the correct insertion point - index := sort.Search(len(p.pkgsDir), func(i int) bool { - return len(p.pkgsDir[i]) <= len(dir) // Longest paths first - }) + newPkgs[dir] = struct{}{} + } - // Check for duplicates - if index < len(p.pkgsDir) && p.pkgsDir[index] == dir { - continue // Skip + for dir := range oldPkgs { + if _, exists := newPkgs[dir]; !exists { + p.watcher.Remove(dir) + p.logger.Debug("Watcher list: removed", "path", dir) } + } - // Insert the package - p.pkgsDir = append(p.pkgsDir[:index], append([]string{abs}, p.pkgsDir[index:]...)...) - - // Add the package to the watcher and handle any errors - if err := p.watcher.Add(abs); err != nil { - return fmt.Errorf("unable to watch %q: %w", pkg.Dir, err) + for dir := range newPkgs { + if _, exists := oldPkgs[dir]; !exists { + p.watcher.Add(dir) + p.logger.Debug("Watcher list: added", "path", dir) } } - - return nil } func (p *PackageWatcher) generatePackagesUpdateList(paths []string) PackageUpdateList { pkgsUpdate := []events.PackageUpdate{} mpkgs := map[string]*events.PackageUpdate{} // Pkg -> Update - for _, path := range paths { - for _, pkg := range p.pkgsDir { - dirPath := filepath.Dir(path) + watchList := p.watcher.WatchList() + for _, file := range paths { + for _, watchDir := range watchList { + if len(watchDir) == len(file) { + continue // Skip if watchDir == file + } // Check if a package directory contain our path directory - if !strings.HasPrefix(pkg, dirPath) { + dir := filepath.Dir(file) + if !strings.HasPrefix(watchDir, dir) { continue } - if len(pkg) == len(path) { - continue // Skip if pkg == path - } - // Accumulate file updates for each package - pkgu, ok := mpkgs[pkg] + pkgu, ok := mpkgs[watchDir] if !ok { pkgsUpdate = append(pkgsUpdate, events.PackageUpdate{ - Package: pkg, - Files: []string{}, + PackageDir: watchDir, + Files: []string{}, }) pkgu = &pkgsUpdate[len(pkgsUpdate)-1] } - pkgu.Files = append(pkgu.Files, path) + pkgu.Files = append(pkgu.Files, file) } } @@ -188,7 +188,7 @@ type PackageUpdateList []events.PackageUpdate func (pkgsu PackageUpdateList) PackagesPath() []string { pkgs := make([]string, len(pkgsu)) for i, pkg := range pkgsu { - pkgs[i] = pkg.Package + pkgs[i] = pkg.PackageDir } return pkgs } diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index 3d1e5f54c54..0a862162331 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -1,13 +1,11 @@ module github.com/gnolang/gno/contribs/gnofaucet -go 1.22 - -toolchain go1.22.4 +go 1.23.6 require ( github.com/gnolang/faucet v0.3.2 github.com/gnolang/gno v0.1.0-nightly.20240627 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 golang.org/x/time v0.5.0 ) @@ -25,34 +23,36 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnofaucet/go.sum b/contribs/gnofaucet/go.sum index 10e2c19b408..e6743b75960 100644 --- a/contribs/gnofaucet/go.sum +++ b/contribs/gnofaucet/go.sum @@ -63,6 +63,8 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -77,8 +79,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -105,8 +107,8 @@ github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkM github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -120,28 +122,32 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -153,22 +159,22 @@ go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -178,34 +184,34 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnogenesis/go.mod b/contribs/gnogenesis/go.mod index 3056af1d4cc..e03ad1dde42 100644 --- a/contribs/gnogenesis/go.mod +++ b/contribs/gnogenesis/go.mod @@ -1,10 +1,10 @@ module github.com/gnolang/contribs/gnogenesis -go 1.22 +go 1.23.6 require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 ) replace github.com/gnolang/gno => ../.. @@ -15,7 +15,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -24,7 +24,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect @@ -34,29 +34,31 @@ require ( github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnogenesis/go.sum b/contribs/gnogenesis/go.sum index 7e4a683cad1..e3462f9c431 100644 --- a/contribs/gnogenesis/go.sum +++ b/contribs/gnogenesis/go.sum @@ -32,8 +32,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 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= @@ -78,8 +78,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -114,8 +114,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -129,53 +129,57 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -185,33 +189,33 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnogenesis/internal/verify/verify.go b/contribs/gnogenesis/internal/verify/verify.go index 9022711ce49..c69f41cad4d 100644 --- a/contribs/gnogenesis/internal/verify/verify.go +++ b/contribs/gnogenesis/internal/verify/verify.go @@ -12,7 +12,10 @@ import ( "github.com/gnolang/gno/tm2/pkg/commands" ) -var errInvalidGenesisState = errors.New("invalid genesis state type") +var ( + errInvalidGenesisState = errors.New("invalid genesis state type") + errInvalidTxSignature = errors.New("invalid tx signature") +) type verifyCfg struct { common.Cfg @@ -60,10 +63,32 @@ func execVerify(cfg *verifyCfg, io commands.IO) error { } // Validate the initial transactions - for _, tx := range state.Txs { + for index, tx := range state.Txs { if validateErr := tx.Tx.ValidateBasic(); validateErr != nil { return fmt.Errorf("invalid transacton, %w", validateErr) } + + // Genesis txs can only be signed by 1 account. + // Basic tx validation ensures there is at least 1 signer + signer := tx.Tx.GetSignatures()[0] + + // Grab the signature bytes of the tx. + // Genesis transactions are signed with + // account number and sequence set to 0 + signBytes, err := tx.Tx.GetSignBytes(genesis.ChainID, 0, 0) + if err != nil { + return fmt.Errorf("unable to get tx signature payload, %w", err) + } + + // Verify the signature using the public key + if !signer.PubKey.VerifyBytes(signBytes, signer.Signature) { + return fmt.Errorf( + "%w #%d, by signer %s", + errInvalidTxSignature, + index, + signer.PubKey.Address(), + ) + } } // Validate the initial balances diff --git a/contribs/gnogenesis/internal/verify/verify_test.go b/contribs/gnogenesis/internal/verify/verify_test.go index 130bd5e09bc..cc80c0423de 100644 --- a/contribs/gnogenesis/internal/verify/verify_test.go +++ b/contribs/gnogenesis/internal/verify/verify_test.go @@ -8,8 +8,12 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" "github.com/gnolang/gno/tm2/pkg/crypto/mock" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/testutils" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -63,6 +67,99 @@ func TestGenesis_Verify(t *testing.T) { require.Error(t, cmdErr) }) + t.Run("invalid tx signature", func(t *testing.T) { + t.Parallel() + + g := getValidTestGenesis() + + testTable := []struct { + name string + signBytesFn func(tx *std.Tx) []byte + }{ + { + name: "invalid chain ID", + signBytesFn: func(tx *std.Tx) []byte { + // Sign the transaction, but with a chain ID + // that differs from the genesis chain ID + signBytes, err := tx.GetSignBytes(g.ChainID+"wrong", 0, 0) + require.NoError(t, err) + + return signBytes + }, + }, + { + name: "invalid account params", + signBytesFn: func(tx *std.Tx) []byte { + // Sign the transaction, but with an + // account number that is not 0 + signBytes, err := tx.GetSignBytes(g.ChainID, 10, 0) + require.NoError(t, err) + + return signBytes + }, + }, + } + + for _, testCase := range testTable { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + tempFile, cleanup := testutils.NewTestFile(t) + t.Cleanup(cleanup) + + // Generate the transaction + signer := ed25519.GenPrivKey() + + sendMsg := bank.MsgSend{ + FromAddress: signer.PubKey().Address(), + ToAddress: signer.PubKey().Address(), + Amount: std.NewCoins(std.NewCoin("ugnot", 10)), + } + + tx := std.Tx{ + Msgs: []std.Msg{sendMsg}, + Fee: std.Fee{ + GasWanted: 1000000, + GasFee: std.NewCoin("ugnot", 20), + }, + } + + // Sign the transaction + signBytes := testCase.signBytesFn(&tx) + + signature, err := signer.Sign(signBytes) + require.NoError(t, err) + + tx.Signatures = append(tx.Signatures, std.Signature{ + PubKey: signer.PubKey(), + Signature: signature, + }) + + g.AppState = gnoland.GnoGenesisState{ + Balances: []gnoland.Balance{}, + Txs: []gnoland.TxWithMetadata{ + { + Tx: tx, + }, + }, + } + + require.NoError(t, g.SaveAs(tempFile.Name())) + + // Create the command + cmd := NewVerifyCmd(commands.NewTestIO()) + args := []string{ + "--genesis-path", + tempFile.Name(), + } + + // Run the command + cmdErr := cmd.ParseAndRun(context.Background(), args) + assert.ErrorIs(t, cmdErr, errInvalidTxSignature) + }) + } + }) + t.Run("invalid balances", func(t *testing.T) { t.Parallel() diff --git a/contribs/gnohealth/go.mod b/contribs/gnohealth/go.mod index 4a3f6392804..81c0ae7325b 100644 --- a/contribs/gnohealth/go.mod +++ b/contribs/gnohealth/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnohealth -go 1.22.4 +go 1.23.6 replace github.com/gnolang/gno => ../.. @@ -16,32 +16,34 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect - github.com/stretchr/testify v1.9.0 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/crypto v0.26.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnohealth/go.sum b/contribs/gnohealth/go.sum index 02e8893406a..3c8b5de45f2 100644 --- a/contribs/gnohealth/go.sum +++ b/contribs/gnohealth/go.sum @@ -57,6 +57,8 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -71,8 +73,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -99,8 +101,8 @@ github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkM github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= @@ -112,47 +114,51 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -162,32 +168,32 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnohealth/health.go b/contribs/gnohealth/health.go deleted file mode 100644 index 5118cac5fa5..00000000000 --- a/contribs/gnohealth/health.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "github.com/gnolang/gno/contribs/gnohealth/internal/timestamp" - "github.com/gnolang/gno/tm2/pkg/commands" -) - -func newHealthCmd(io commands.IO) *commands.Command { - cmd := commands.NewCommand( - commands.Metadata{ - ShortUsage: " [flags] [...]", - ShortHelp: "gno health check suite", - LongHelp: "Gno health check suite, to verify that different parts of Gno are working correctly", - }, - commands.NewEmptyConfig(), - commands.HelpExec, - ) - - cmd.AddSubCommands( - timestamp.NewTimestampCmd(io), - ) - - return cmd -} diff --git a/contribs/gnohealth/internal/timestamp/timestamp.go b/contribs/gnohealth/internal/timestamp/timestamp.go index 50521b9130f..cb5dab44f91 100644 --- a/contribs/gnohealth/internal/timestamp/timestamp.go +++ b/contribs/gnohealth/internal/timestamp/timestamp.go @@ -35,7 +35,7 @@ func NewTimestampCmd(io commands.IO) *commands.Command { return commands.NewCommand( commands.Metadata{ Name: "timestamp", - ShortUsage: "[flags]", + ShortUsage: "timestamp [flags]", ShortHelp: "check if block timestamps are drifting", LongHelp: "This command checks if block timestamps are drifting on a blockchain by connecting to a specified node via RPC.", }, diff --git a/contribs/gnohealth/main.go b/contribs/gnohealth/main.go index 4325c657976..a4a23369710 100644 --- a/contribs/gnohealth/main.go +++ b/contribs/gnohealth/main.go @@ -4,11 +4,24 @@ import ( "context" "os" + "github.com/gnolang/gno/contribs/gnohealth/internal/timestamp" "github.com/gnolang/gno/tm2/pkg/commands" ) func main() { - cmd := newHealthCmd(commands.NewDefaultIO()) + cmd := commands.NewCommand( + commands.Metadata{ + ShortUsage: " [flags]", + LongHelp: "Gno health check suite, to verify that different parts of Gno are working correctly", + }, + commands.NewEmptyConfig(), + commands.HelpExec, + ) + + io := commands.NewDefaultIO() + cmd.AddSubCommands( + timestamp.NewTimestampCmd(io), + ) cmd.Execute(context.Background(), os.Args[1:]) } diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 157b5585828..0ecc7b34cf3 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -1,8 +1,6 @@ module github.com/gnolang/gno/contribs/gnokeykc -go 1.22 - -toolchain go1.22.4 +go 1.23.6 replace github.com/gnolang/gno => ../.. @@ -17,7 +15,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // 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.3.0 // indirect @@ -28,7 +26,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect @@ -36,30 +34,32 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index 7aac05b84a0..6b4f81dfcf5 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -34,8 +34,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -84,8 +84,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -120,8 +120,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= @@ -135,10 +135,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -147,43 +149,45 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -193,33 +197,33 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/contribs/gnomd/Makefile b/contribs/gnomd/Makefile index f953631b529..984c809502a 100644 --- a/contribs/gnomd/Makefile +++ b/contribs/gnomd/Makefile @@ -1,3 +1,8 @@ +rundep := go run -modfile ../../misc/devdeps/go.mod +golangci_lint := $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint + + +.PHONY: install test lint install: go install . @@ -5,4 +10,4 @@ test: @echo "XXX: add tests" lint: - @echo "XXX: add lint" + $(golangci_lint) --config ../../.github/golangci.yml run ./... diff --git a/contribs/gnomd/go.mod b/contribs/gnomd/go.mod index 57c07621324..d8f01bf82e3 100644 --- a/contribs/gnomd/go.mod +++ b/contribs/gnomd/go.mod @@ -8,20 +8,19 @@ require github.com/MichaelMure/go-term-markdown v0.1.4 require ( github.com/MichaelMure/go-term-text v0.3.1 // indirect - github.com/alecthomas/chroma v0.7.1 // indirect - github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect + github.com/alecthomas/chroma v0.10.0 // indirect github.com/disintegration/imaging v1.6.2 // indirect - github.com/dlclark/regexp2 v1.1.6 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75 // indirect - github.com/fatih/color v1.9.0 // indirect - github.com/gomarkdown/markdown v0.0.0-20191123064959-2c17d62f5098 // indirect - github.com/kyokomi/emoji/v2 v2.2.8 // indirect - github.com/lucasb-eyer/go-colorful v1.0.3 // indirect - github.com/mattn/go-colorable v0.1.4 // indirect - github.com/mattn/go-isatty v0.0.11 // indirect - github.com/mattn/go-runewidth v0.0.12 // indirect - github.com/rivo/uniseg v0.1.0 // indirect - golang.org/x/image v0.18.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62 // indirect + github.com/kyokomi/emoji/v2 v2.2.13 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/image v0.23.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect ) diff --git a/contribs/gnomd/go.sum b/contribs/gnomd/go.sum index 3d4666530b1..cfa2672ae06 100644 --- a/contribs/gnomd/go.sum +++ b/contribs/gnomd/go.sum @@ -2,50 +2,57 @@ github.com/MichaelMure/go-term-markdown v0.1.4 h1:Ir3kBXDUtOX7dEv0EaQV8CNPpH+T7A github.com/MichaelMure/go-term-markdown v0.1.4/go.mod h1:EhcA3+pKYnlUsxYKBJ5Sn1cTQmmBMjeNlpV8nRb+JxA= github.com/MichaelMure/go-term-text v0.3.1 h1:Kw9kZanyZWiCHOYu9v/8pWEgDQ6UVN9/ix2Vd2zzWf0= github.com/MichaelMure/go-term-text v0.3.1/go.mod h1:QgVjAEDUnRMlzpS6ky5CGblux7ebeiLnuy9dAaFZu8o= -github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= -github.com/alecthomas/chroma v0.7.1 h1:G1i02OhUbRi2nJxcNkwJaY/J1gHXj9tt72qN6ZouLFQ= github.com/alecthomas/chroma v0.7.1/go.mod h1:gHw09mkX1Qp80JlYbmN9L3+4R5o6DJJ3GRShh+AICNc= -github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= -github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= -github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg= github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75 h1:vbix8DDQ/rfatfFr/8cf/sJfIL69i4BcZfjrVOxsMqk= github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75/go.mod h1:0gZuvTO1ikSA5LtTI6E13LEOdWQNjIo5MTQOvrV0eFg= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/gomarkdown/markdown v0.0.0-20191123064959-2c17d62f5098 h1:Qxs3bNRWe8GTcKMxYOSXm0jx6j0de8XUtb/fsP3GZ0I= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/gomarkdown/markdown v0.0.0-20191123064959-2c17d62f5098/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU= -github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE= +github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62 h1:pbAFUZisjG4s6sxvRJvf2N7vhpCvx2Oxb3PmS6pDO1g= +github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= -github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U= +github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -56,17 +63,18 @@ golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90te golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191206065243-da761ea9ff43/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/contribs/gnomd/main.go b/contribs/gnomd/main.go index 2d3b7266af5..85dc67547f3 100644 --- a/contribs/gnomd/main.go +++ b/contribs/gnomd/main.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "io/ioutil" + "io" "os" markdown "github.com/MichaelMure/go-term-markdown" @@ -11,14 +11,14 @@ import ( func main() { // If no arguments are provided, read from stdin if len(os.Args) <= 1 { - fileContent, err := ioutil.ReadAll(os.Stdin) + fileContent, err := io.ReadAll(os.Stdin) checkErr(err) renderMarkdown("stdin.gno", fileContent) } // Iterate through command-line arguments (file paths) for _, filePath := range os.Args[1:] { - fileContent, err := ioutil.ReadFile(filePath) + fileContent, err := os.ReadFile(filePath) checkErr(err) renderMarkdown(filePath, fileContent) } diff --git a/contribs/gnomigrate/go.mod b/contribs/gnomigrate/go.mod index 49f40eb79af..0381c6acd01 100644 --- a/contribs/gnomigrate/go.mod +++ b/contribs/gnomigrate/go.mod @@ -1,10 +1,10 @@ module github.com/gnolang/gnomigrate -go 1.22 +go 1.23.6 require ( github.com/gnolang/gno v0.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 ) replace github.com/gnolang/gno => ../.. @@ -22,7 +22,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect @@ -31,27 +31,29 @@ require ( github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/contribs/gnomigrate/go.sum b/contribs/gnomigrate/go.sum index 7e4a683cad1..e3462f9c431 100644 --- a/contribs/gnomigrate/go.sum +++ b/contribs/gnomigrate/go.sum @@ -32,8 +32,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 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= @@ -78,8 +78,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -114,8 +114,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -129,53 +129,57 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -185,33 +189,33 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/docs/Makefile b/docs/Makefile index c642cdbbe8e..96bdc4f2f40 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,10 +1,8 @@ -all: build lint +lint: + go run -C ../misc/docs/tools/linter . -path "$(PWD)" -treat-urls-as-err=true -# Build the linter -build: +generate: go run -modfile ../misc/devdeps/go.mod github.com/campoy/embedmd -w `find . -name "*.md"` -# Run the linter for the docs/ folder -lint: - go build -C ../misc/docs-linter -o ./build - ../misc/docs-linter/build -path . +test: + @echo "Nothing to do." diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000000..ff8bf5e6e49 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,50 @@ +# gno.land + +Welcome to the official documentation of gno.land. + +gno.land is a smart contract platform built on a novel interpretation of Go +(Golang) for blockchain, created to enable powerful, modular, and secure +decentralized applications with a minimalist approach and excellent developer +experience. + +## Use gno.land + +Start exploring and using gno.land. Setup essential tools, interact with the +chain and its dApps. + +- [Discover gno.land](users/discover-gnoland.md) - Discover the gno.land ecosystem and explore available tools for interacting with the blockchain. +- [Using the `gnoweb` interface](users/explore-with-gnoweb.md) - Learn how to use `gnoweb` to browse realm code, view rendered content, and explore the gno.land ecosystem. +- [Installing and using the `gnokey` wallet](users/interact-with-gnokey.md) - Learn how to manage keys and interact with gno.land networks using the `gnokey` command-line tool. +- [Example `boards`](users/example-boards.md) - Follow a complete workflow for using and interacting with community board realms. +- [Third-party wallets](users/third-party-wallets.md) - Discover compatible third-party wallets and how to use them with gno.land networks. +- [Become a power user](users/power-users.md) - Take your gno.land journey further with social networks, community resources, and development tools. + +## Build on gno.land + +Get started with Gno development. Learn how to work with Gno tools, use +Gno-specific features, connect to Gno apps with clients, and more. + +- [What is Gno](builders/what-is-gnolang.md) - An introduction to Gno, a blockchain implementation of Go created by Cosmos co-founder Jae Kwon, highlighting its similarities to Go, security features, and advantages over other smart contract languages. +- [Writing Gno code](builders/anatomy-of-a-gno-package.md) - Get a grasp on the basics of Gno syntax by building a simple Counter application and understanding the core language features. +- [Installing `gno` and developing locally with `gnodev`](builders/local-dev-with-gnodev.md) - Setup a local development environment with a built-in node, hot-reload features, and testing capabilities. +- [Example `minisocial` dApp](builders/example-minisocial-dapp.md) - Build a complete social media application while learning the full local development workflow for Gno packages. +- [Deploying Gno packages](builders/deploy-packages.md) - Learn how to deploy your code to gno.land networks using `gnokey` and understand gas requirements. +- [Connecting clients](builders/connect-clients-and-apps.md) - Discover how to connect external applications to gno.land networks using both Go and JavaScript clients. +- [Become a Gnome](builders/become-a-gnome.md) - Learn what makes a great gno.land contributor and how to showcase your work in the ecosystem. + +## Resources + +Learn about core concepts found in gno.land & Gno. + +- [Awesome Gno](resources/awesome-gno.md) - Curated list of Gno freshness. +- [Effective Gno](resources/effective-gno.md) - Learn how to write Gno following best practices, including counter-intuitive good practices specific to blockchain +- [Packages](resources/gno-packages.md) - Learn about realms, pure packages, smart contracts, dApps and how they're organized in the gno.land ecosystem. +- [Networks](resources/gnoland-networks.md) - Discover different gno.land networks (testnets, devnets) and their purposes, including network configurations. +- [Users and Teams](resources/users-and-teams.md) - Understand user registration, namespace ownership, and team collaboration in the gno.land ecosystem. +- [Gas Fees](resources/gas-fees.md) - Learn about gas pricing, estimation, and optimization strategies in gno.land. +- [Standard libraries](resources/gno-stdlibs.md) - An overview of the standard libraries found in the Gno language and how they enhance blockchain functionality. +- [Glossary of Gno terms](resources/glossary.md) - List of common terms found in the gno.land ecosystem, from technical concepts to tools and components. +- [Testing Gno](resources/gno-testing.md) - Learn how to run and test Gno code locally using the built-in testing framework. +- [Go - Gno compatibility](resources/go-gno-compatibility.md) - A detailed compatibility list between Go and Gno features, including supported keywords, types, and standard libraries. +- [Gno Examples](https://github.com/gnolang/gno/tree/master/examples) - A large library of existing pure packages and realms to use and learn from. +- [Gno Workshops](https://github.com/gnolang/workshops) - Previous workshops and presentations by the gno.land team and the community. diff --git a/docs/_assets/minisocial/posts-0.gno b/docs/_assets/minisocial/posts-0.gno new file mode 100644 index 00000000000..f249816871b --- /dev/null +++ b/docs/_assets/minisocial/posts-0.gno @@ -0,0 +1,3 @@ +package minisocial + +var posts []*Post diff --git a/docs/_assets/minisocial/posts-1.gno b/docs/_assets/minisocial/posts-1.gno new file mode 100644 index 00000000000..cc301afc79e --- /dev/null +++ b/docs/_assets/minisocial/posts-1.gno @@ -0,0 +1,26 @@ +package minisocial + +import ( + "errors" // For handling errors + "std" // The standard Gno package + "time" // For handling time +) + +var posts []*Post + +// CreatePost creates a new post +func CreatePost(text string) error { + // If the body of the post is empty, return an error + if text == "" { + return errors.New("empty post text") + } + + // Append the new post to the list + posts = append(posts, &Post{ + text: text, // Set the input text + author: std.PreviousRealm().Address(), // The author of the address is the previous realm, the realm that called this one + createdAt: time.Now(), // Capture the time of the transaction, in this case the block timestamp + }) + + return nil +} diff --git a/docs/_assets/minisocial/posts_test-0.gno b/docs/_assets/minisocial/posts_test-0.gno new file mode 100644 index 00000000000..a4972e4fb43 --- /dev/null +++ b/docs/_assets/minisocial/posts_test-0.gno @@ -0,0 +1,30 @@ +package minisocial + +import ( + "std" + "strings" + "testing" + + "gno.land/p/demo/testutils" // Provides testing utilities +) + +func TestCreatePostSingle(t *testing.T) { + // Get a test address for alice + aliceAddr := testutils.TestAddress("alice") + // TestSetRealm sets the realm caller, in this case Alice + std.TestSetRealm(std.NewUserRealm(aliceAddr)) + + text1 := "Hello World!" + err := CreatePost(text1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + // Get the rendered page + got := Render("") + + // Content should have the text and alice's address in it + if !(strings.Contains(got, text1) && strings.Contains(got, aliceAddr.String())) { + t.Fatal("expected render to contain text & alice's address") + } +} diff --git a/docs/_assets/minisocial/posts_test-1.gno b/docs/_assets/minisocial/posts_test-1.gno new file mode 100644 index 00000000000..ac0125772d4 --- /dev/null +++ b/docs/_assets/minisocial/posts_test-1.gno @@ -0,0 +1,67 @@ +package minisocial + +import ( + "std" + "strings" + "testing" + + "gno.land/p/demo/testutils" // Provides testing utilities +) + +func TestCreatePostSingle(t *testing.T) { + // Get a test address for alice + aliceAddr := testutils.TestAddress("alice") + // TestSetRealm sets the realm caller, in this case Alice + std.TestSetRealm(std.NewUserRealm(aliceAddr)) + + text1 := "Hello World!" + err := CreatePost(text1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + // Get the rendered page + got := Render("") + + // Content should have the text and alice's address in it + if !(strings.Contains(got, text1) && strings.Contains(got, aliceAddr.String())) { + t.Fatal("expected render to contain text & alice's address") + } +} + +func TestCreatePostMultiple(t *testing.T) { + // Initialize a slice to hold the test posts and their authors + posts := []struct { + text string + author string + }{ + {"Hello World!", "alice"}, + {"This is some new text!", "bob"}, + {"Another post by alice", "alice"}, + {"A post by charlie!", "charlie"}, + } + + for _, p := range posts { + // Set the appropriate caller realm based on the author + authorAddr := testutils.TestAddress(p.author) + std.TestSetRealm(std.NewUserRealm(authorAddr)) + + // Create the post + err := CreatePost(p.text) + if err != nil { + t.Fatalf("expected no error for post '%s', got %v", p.text, err) + } + } + + // Get the rendered page + got := Render("") + + // Check that all posts and their authors are present in the rendered output + for _, p := range posts { + expectedText := p.text + expectedAuthor := testutils.TestAddress(p.author).String() // Get the address for the author + if !(strings.Contains(got, expectedText) && strings.Contains(got, expectedAuthor)) { + t.Fatalf("expected render to contain text '%s' and address '%s'", expectedText, expectedAuthor) + } + } +} diff --git a/docs/_assets/minisocial/render-0.gno b/docs/_assets/minisocial/render-0.gno new file mode 100644 index 00000000000..ab4576fc4d2 --- /dev/null +++ b/docs/_assets/minisocial/render-0.gno @@ -0,0 +1,18 @@ +package minisocial + +func Render(_ string) string { + output := "# MiniSocial\n\n" // \n is needed just like in standard Markdown + + // Handle the edge case + if len(posts) == 0 { + output += "No posts.\n" + return output + } + + // Let's append the text of each post to the output + for _, post := range posts { + output += post.text + "\n\n" + } + + return output +} diff --git a/docs/_assets/minisocial/render-1.gno b/docs/_assets/minisocial/render-1.gno new file mode 100644 index 00000000000..0487c7015d7 --- /dev/null +++ b/docs/_assets/minisocial/render-1.gno @@ -0,0 +1,25 @@ +package minisocial + +import "gno.land/p/demo/ufmt" // Gno counterpart to `fmt`, for formatting strings + +func Render(_ string) string { + output := "# MiniSocial\n\n" // \n is needed just like in standard Markdown + + // Handle the edge case + if len(posts) == 0 { + output += "No posts.\n" + return output + } + + // Let's append the text of each post to the output + for i, post := range posts { + // Let's append some post metadata + output += ufmt.Sprintf("#### Post #%d\n\n", i) + // Add the stringified post + output += post.String() + // Add a line break for cleaner UI + output += "---\n\n" + } + + return output +} diff --git a/docs/_assets/minisocial/types-1.gno b/docs/_assets/minisocial/types-1.gno new file mode 100644 index 00000000000..5b0a64cca1d --- /dev/null +++ b/docs/_assets/minisocial/types-1.gno @@ -0,0 +1,13 @@ +package minisocial + +import ( + "std" // The standard Gno package + "time" // For handling time operations +) + +// Post defines the main data we keep about each post +type Post struct { + text string // Main text body + author std.Address // Address of the post author, provided by the execution context + createdAt time.Time // When the post was created +} diff --git a/docs/_assets/minisocial/types-2-bonus.gno b/docs/_assets/minisocial/types-2-bonus.gno new file mode 100644 index 00000000000..b90ad667ea0 --- /dev/null +++ b/docs/_assets/minisocial/types-2-bonus.gno @@ -0,0 +1,34 @@ +package minisocial + +import ( + "std" // The standard Gno package + "time" // For handling time operations + + "gno.land/p/demo/ufmt" + + "gno.land/r/sys/users" +) + +// Post defines the main data we keep about each post +type Post struct { + text string + author std.Address + createdAt time.Time +} + +// String stringifies a Post +func (p Post) String() string { + out := p.text + "\n\n" + + author := p.author.String() + // We can import and use the r/sys/users package to resolve addresses + user, _ := users.ResolveAddress(p.author) + if user != nil { + // RenderLink provides a link that is clickable + // The link goes to the user's profile page + author = user.RenderLink() + } + + out += ufmt.Sprintf("_by %s on %s_\n\n", author, p.createdAt.Format("02 Jan 2006, 15:04")) + return out +} diff --git a/docs/_assets/minisocial/types-2.gno b/docs/_assets/minisocial/types-2.gno new file mode 100644 index 00000000000..9f3b0a56847 --- /dev/null +++ b/docs/_assets/minisocial/types-2.gno @@ -0,0 +1,26 @@ +package minisocial + +import ( + "std" // The standard Gno package + "time" // For handling time operations + + "gno.land/p/demo/ufmt" +) + +// Post defines the main data we keep about each post +type Post struct { + text string + author std.Address + createdAt time.Time +} + +// String stringifies a Post +func (p Post) String() string { + out := p.text + "\n\n" + + // We can use `ufmt` to format strings, and the built-in time library formatting function + out += ufmt.Sprintf("_by %s on %s_, ", p.author, p.createdAt.Format("02 Jan 2006, 15:04")) + out += "\n\n" + + return out +} diff --git a/docs/assets/explanation/packages/pkg-1.gno b/docs/assets/explanation/packages/pkg-1.gno deleted file mode 100644 index e68d506612a..00000000000 --- a/docs/assets/explanation/packages/pkg-1.gno +++ /dev/null @@ -1,6 +0,0 @@ -func TotalSupply() uint64 -func BalanceOf(account std.Address) uint64 -func Transfer(to std.Address, amount uint64) -func Approve(spender std.Address, amount uint64) -func TransferFrom(from, to std.Address, amount uint64) -func Allowance(owner, spender std.Address) uint64 diff --git a/docs/assets/explanation/packages/pkg-2.gno b/docs/assets/explanation/packages/pkg-2.gno deleted file mode 100644 index 0054cc95e3d..00000000000 --- a/docs/assets/explanation/packages/pkg-2.gno +++ /dev/null @@ -1,11 +0,0 @@ -// functions that work similarly to those of grc20 -func BalanceOf(owner std.Address) (uint64, error) -func Approve(approved std.Address, tid TokenID) error -func TransferFrom(from, to std.Address, tid TokenID) error - -// functions unique to grc721 -func OwnerOf(tid TokenID) (std.Address, error) -func SafeTransferFrom(from, to std.Address, tid TokenID) error -func SetApprovalForAll(operator std.Address, approved bool) error -func GetApproved(tid TokenID) (std.Address, error) -func IsApprovedForAll(owner, operator std.Address) bool diff --git a/docs/assets/explanation/packages/pkg-3.gno b/docs/assets/explanation/packages/pkg-3.gno deleted file mode 100644 index f1ba5609d6b..00000000000 --- a/docs/assets/explanation/packages/pkg-3.gno +++ /dev/null @@ -1,12 +0,0 @@ -func TestAddress(name string) std.Address { - if len(name) > std.RawAddressSize { - panic("address name cannot be greater than std.AddressSize bytes") - } - addr := std.RawAddress{} - // TODO: use strings.RepeatString or similar. - // NOTE: I miss python's "".Join(). - blanks := "____________________" - copy(addr[:], []byte(blanks)) - copy(addr[:], []byte(name)) - return std.Address(std.EncodeBech32("g", addr)) -} diff --git a/docs/assets/explanation/packages/pkg-4.gno b/docs/assets/explanation/packages/pkg-4.gno deleted file mode 100644 index edd34b5cc5d..00000000000 --- a/docs/assets/explanation/packages/pkg-4.gno +++ /dev/null @@ -1,8 +0,0 @@ -admin := users.AddressOrName("g1tntwtvzrkt2gex69f0pttan0fp05zmeg5yykv8") -test2 := users.AddressOrName(testutils.TestAddress("test2")) -recv := users.AddressOrName(testutils.TestAddress("recv")) -normal := users.AddressOrName(testutils.TestAddress("normal")) -owner := users.AddressOrName(testutils.TestAddress("owner")) -spender := users.AddressOrName(testutils.TestAddress("spender")) -recv2 := users.AddressOrName(testutils.TestAddress("recv2")) -mibu := users.AddressOrName(testutils.TestAddress("mint_burn")) diff --git a/docs/assets/getting-started/local-setup/browsing-gno-source-code/blog_help.png b/docs/assets/getting-started/local-setup/browsing-gno-source-code/blog_help.png deleted file mode 100644 index ad81aace478..00000000000 Binary files a/docs/assets/getting-started/local-setup/browsing-gno-source-code/blog_help.png and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/browsing-gno-source-code/blog_render.png b/docs/assets/getting-started/local-setup/browsing-gno-source-code/blog_render.png deleted file mode 100644 index dbb5c4c6fee..00000000000 Binary files a/docs/assets/getting-started/local-setup/browsing-gno-source-code/blog_render.png and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/browsing-gno-source-code/blog_source.png b/docs/assets/getting-started/local-setup/browsing-gno-source-code/blog_source.png deleted file mode 100644 index 4031fac3383..00000000000 Binary files a/docs/assets/getting-started/local-setup/browsing-gno-source-code/blog_source.png and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/browsing-gno-source-code/gnodev.gif b/docs/assets/getting-started/local-setup/browsing-gno-source-code/gnodev.gif deleted file mode 100644 index 6a718867da9..00000000000 Binary files a/docs/assets/getting-started/local-setup/browsing-gno-source-code/gnodev.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/browsing-gno-source-code/gnoland-homepage.png b/docs/assets/getting-started/local-setup/browsing-gno-source-code/gnoland-homepage.png deleted file mode 100644 index ba231aa252e..00000000000 Binary files a/docs/assets/getting-started/local-setup/browsing-gno-source-code/gnoland-homepage.png and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/browsing-gno-source-code/gnoweb-avl.png b/docs/assets/getting-started/local-setup/browsing-gno-source-code/gnoweb-avl.png deleted file mode 100644 index 61e1936648f..00000000000 Binary files a/docs/assets/getting-started/local-setup/browsing-gno-source-code/gnoweb-avl.png and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-mnemonic.gif b/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-mnemonic.gif deleted file mode 100644 index 641acc9f825..00000000000 Binary files a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-mnemonic.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-random.gif b/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-random.gif deleted file mode 100644 index dba61a6b287..00000000000 Binary files a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-random.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-export.gif b/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-export.gif deleted file mode 100644 index e56938866f9..00000000000 Binary files a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-export.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-generate.gif b/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-generate.gif deleted file mode 100644 index ce371487c6c..00000000000 Binary files a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-generate.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-import.gif b/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-import.gif deleted file mode 100644 index 8ec80519372..00000000000 Binary files a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-import.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-list.gif b/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-list.gif deleted file mode 100644 index ad050da585c..00000000000 Binary files a/docs/assets/getting-started/local-setup/creating-a-key-pair/gnokey-list.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/interacting-with-gnoland/faucet-hub-portal-loop.png b/docs/assets/getting-started/local-setup/interacting-with-gnoland/faucet-hub-portal-loop.png deleted file mode 100644 index fb3455f4a55..00000000000 Binary files a/docs/assets/getting-started/local-setup/interacting-with-gnoland/faucet-hub-portal-loop.png and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/interacting-with-gnoland/faucet-hub.png b/docs/assets/getting-started/local-setup/interacting-with-gnoland/faucet-hub.png deleted file mode 100644 index 18d611c26a0..00000000000 Binary files a/docs/assets/getting-started/local-setup/interacting-with-gnoland/faucet-hub.png and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/interacting-with-gnoland/userbook-default.png b/docs/assets/getting-started/local-setup/interacting-with-gnoland/userbook-default.png deleted file mode 100644 index f275e71d18e..00000000000 Binary files a/docs/assets/getting-started/local-setup/interacting-with-gnoland/userbook-default.png and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/interacting-with-gnoland/userbook-help.png b/docs/assets/getting-started/local-setup/interacting-with-gnoland/userbook-help.png deleted file mode 100644 index a2a4a5182d6..00000000000 Binary files a/docs/assets/getting-started/local-setup/interacting-with-gnoland/userbook-help.png and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/local-setup/gno-help.gif b/docs/assets/getting-started/local-setup/local-setup/gno-help.gif deleted file mode 100644 index ebd7d4cfcb0..00000000000 Binary files a/docs/assets/getting-started/local-setup/local-setup/gno-help.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/local-setup/gnodev.gif b/docs/assets/getting-started/local-setup/local-setup/gnodev.gif deleted file mode 100644 index 6a718867da9..00000000000 Binary files a/docs/assets/getting-started/local-setup/local-setup/gnodev.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/local-setup/gnokey-help.gif b/docs/assets/getting-started/local-setup/local-setup/gnokey-help.gif deleted file mode 100644 index d96d2424679..00000000000 Binary files a/docs/assets/getting-started/local-setup/local-setup/gnokey-help.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/local-setup/make-build-gnoland.gif b/docs/assets/getting-started/local-setup/local-setup/make-build-gnoland.gif deleted file mode 100644 index fe12670be2c..00000000000 Binary files a/docs/assets/getting-started/local-setup/local-setup/make-build-gnoland.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/local-setup/make-build-gnovm.gif b/docs/assets/getting-started/local-setup/local-setup/make-build-gnovm.gif deleted file mode 100644 index 54f133796d6..00000000000 Binary files a/docs/assets/getting-started/local-setup/local-setup/make-build-gnovm.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif b/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif deleted file mode 100644 index 40c79bd91f6..00000000000 Binary files a/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif b/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif deleted file mode 100644 index 4882aabdfde..00000000000 Binary files a/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/setting-up-funds/faucet-page.png b/docs/assets/getting-started/local-setup/setting-up-funds/faucet-page.png deleted file mode 100644 index f1a5420659f..00000000000 Binary files a/docs/assets/getting-started/local-setup/setting-up-funds/faucet-page.png and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/setting-up-funds/gnofaucet-serve.gif b/docs/assets/getting-started/local-setup/setting-up-funds/gnofaucet-serve.gif deleted file mode 100644 index 79f46e7563c..00000000000 Binary files a/docs/assets/getting-started/local-setup/setting-up-funds/gnofaucet-serve.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/setting-up-funds/gnokey-query.gif b/docs/assets/getting-started/local-setup/setting-up-funds/gnokey-query.gif deleted file mode 100644 index 9f58c638624..00000000000 Binary files a/docs/assets/getting-started/local-setup/setting-up-funds/gnokey-query.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/setting-up-funds/gnoland-start.gif b/docs/assets/getting-started/local-setup/setting-up-funds/gnoland-start.gif deleted file mode 100644 index 3c323c40410..00000000000 Binary files a/docs/assets/getting-started/local-setup/setting-up-funds/gnoland-start.gif and /dev/null differ diff --git a/docs/assets/getting-started/local-setup/setting-up-funds/gnoweb.gif b/docs/assets/getting-started/local-setup/setting-up-funds/gnoweb.gif deleted file mode 100644 index ce4202dcca4..00000000000 Binary files a/docs/assets/getting-started/local-setup/setting-up-funds/gnoweb.gif and /dev/null differ diff --git a/docs/assets/getting-started/playground/default_deploy.png b/docs/assets/getting-started/playground/default_deploy.png deleted file mode 100644 index 0165578b1a7..00000000000 Binary files a/docs/assets/getting-started/playground/default_deploy.png and /dev/null differ diff --git a/docs/assets/getting-started/playground/default_playground.png b/docs/assets/getting-started/playground/default_playground.png deleted file mode 100644 index d42f1fb1cdc..00000000000 Binary files a/docs/assets/getting-started/playground/default_playground.png and /dev/null differ diff --git a/docs/assets/getting-started/playground/run.png b/docs/assets/getting-started/playground/run.png deleted file mode 100644 index ad6be11104f..00000000000 Binary files a/docs/assets/getting-started/playground/run.png and /dev/null differ diff --git a/docs/assets/gno-tooling/gnodev/gnodev.gif b/docs/assets/gno-tooling/gnodev/gnodev.gif deleted file mode 100644 index f3f86bdbb2f..00000000000 Binary files a/docs/assets/gno-tooling/gnodev/gnodev.gif and /dev/null differ diff --git a/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno b/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno deleted file mode 100644 index bbdf84f8a9f..00000000000 --- a/docs/assets/how-to-guides/creating-grc20/mytoken-1.gno +++ /dev/null @@ -1,30 +0,0 @@ -package mytoken - -import ( - "std" - "strings" - - "gno.land/p/demo/grc/grc20" - "gno.land/p/demo/ufmt" -) - -var ( - banker *grc20.Banker - mytoken grc20.Token - admin std.Address -) - -// init is called once at time of deployment -func init() { - // Set deployer of Realm to admin - admin = std.PrevRealm().Addr() - - // Set token name, symbol and number of decimals - banker = grc20.NewBanker("My Token", "TKN", 4) - - // Mint 1 million tokens to admin - banker.Mint(admin, 1_000_000*10_000) // 1M - - // Get the GRC20 compatible safe object - mytoken = banker.Token() -} diff --git a/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno b/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno deleted file mode 100644 index 71616feba15..00000000000 --- a/docs/assets/how-to-guides/creating-grc20/mytoken-2.gno +++ /dev/null @@ -1,88 +0,0 @@ -package mytoken - -import ( - "std" - "strings" - - "gno.land/p/demo/ufmt" -) - -// TotalSupply returns the total supply of mytoken -func TotalSupply() uint64 { - return mytoken.TotalSupply() -} - -// Decimals returns the number of decimals of mytoken -func Decimals() uint { - return mytoken.GetDecimals() -} - -// BalanceOf returns the balance mytoken for `account` -func BalanceOf(account std.Address) uint64 { - return mytoken.BalanceOf(account) -} - -// Allowance returns the allowance of spender on owner's balance -func Allowance(owner, spender std.Address) uint64 { - return mytoken.Allowance(owner, spender) -} - -// Transfer transfers amount from caller to recipient -func Transfer(recipient std.Address, amount uint64) { - checkErr(mytoken.Transfer(recipient, amount)) -} - -// Approve approves amount of caller's tokens to be spent by spender -func Approve(spender std.Address, amount uint64) { - checkErr(mytoken.Approve(spender, amount)) -} - -// TransferFrom transfers `amount` of tokens from `from` to `to` -func TransferFrom(from, to std.Address, amount uint64) { - checkErr(mytoken.TransferFrom(from, to, amount)) -} - -// Mint mints amount of tokens to address. Callable only by admin of token -func Mint(address std.Address, amount uint64) { - assertIsAdmin(std.PrevRealm().Addr()) - checkErr(banker.Mint(address, amount)) -} - -// Burn burns amount of tokens from address. Callable only by admin of token -func Burn(address std.Address, amount uint64) { - assertIsAdmin(std.PrevRealm().Addr()) - checkErr(banker.Burn(address, amount)) -} - -// Render renders the state of the realm -func Render(path string) string { - parts := strings.Split(path, "/") - c := len(parts) - - switch { - case path == "": - // Default GRC20 render - return mytoken.RenderHome() - case c == 2 && parts[0] == "balance": - // Render balance of specific address - owner := std.Address(parts[1]) - balance, _ := mytoken.BalanceOf(owner) - return ufmt.Sprintf("%d\n", balance) - default: - return "404\n" - } -} - -// assertIsAdmin asserts the address is the admin of token -func assertIsAdmin(address std.Address) { - if address != admin { - panic("restricted access") - } -} - -// checkErr asserts the function didn't returned an error -func checkErr(err error) { - if err != nil { - panic(err) - } -} diff --git a/docs/assets/how-to-guides/deploy/deploy_connect.png b/docs/assets/how-to-guides/deploy/deploy_connect.png deleted file mode 100644 index 984f93701b7..00000000000 Binary files a/docs/assets/how-to-guides/deploy/deploy_connect.png and /dev/null differ diff --git a/docs/assets/how-to-guides/deploy/deploy_default.png b/docs/assets/how-to-guides/deploy/deploy_default.png deleted file mode 100644 index 1ad489535e0..00000000000 Binary files a/docs/assets/how-to-guides/deploy/deploy_default.png and /dev/null differ diff --git a/docs/assets/how-to-guides/deploy/deploy_success.png b/docs/assets/how-to-guides/deploy/deploy_success.png deleted file mode 100644 index 9d5a11eb201..00000000000 Binary files a/docs/assets/how-to-guides/deploy/deploy_success.png and /dev/null differ diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-1.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-1.gno deleted file mode 100644 index 1e1dee77a86..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-1.gno +++ /dev/null @@ -1,39 +0,0 @@ -func shouldEqual(t *testing.T, got interface{}, expected interface{}) { - t.Helper() - - if got != expected { - t.Errorf("expected %v(%T), got %v(%T)", expected, expected, got, got) - } -} - -func shouldErr(t *testing.T, err error) { - t.Helper() - if err == nil { - t.Errorf("expected an error, but got nil.") - } -} - -func shouldNoErr(t *testing.T, err error) { - t.Helper() - if err != nil { - t.Errorf("expected no error, but got err: %s.", err.Error()) - } -} - -func shouldPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("should have panic") - } - }() - f() -} - -func shouldNoPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r != nil { - t.Errorf("should not have panic") - } - }() - f() -} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-10.sol b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-10.sol deleted file mode 100644 index 8d7aff1794b..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-10.sol +++ /dev/null @@ -1,29 +0,0 @@ -/// End the auction and send the highest bid -/// to the beneficiary. -function auctionEnd() external { - // It is a good guideline to structure functions that interact - // with other contracts (i.e. they call functions or send Ether) - // into three phases: - // 1. checking conditions - // 2. performing actions (potentially changing conditions) - // 3. interacting with other contracts - // If these phases are mixed up, the other contract could call - // back into the current contract and modify the state or cause - // effects (ether payout) to be performed multiple times. - // If functions called internally include interaction with external - // contracts, they also have to be considered interaction with - // external contracts. - - // 1. Conditions - if (block.timestamp < auctionEndTime) - revert AuctionNotYetEnded(); - if (ended) - revert AuctionEndAlreadyCalled(); - - // 2. Effects - ended = true; - emit AuctionEnded(highestBidder, highestBid); - - // 3. Interaction - beneficiary.transfer(highestBid); -} \ No newline at end of file diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno deleted file mode 100644 index e48ebf919a0..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-11.gno +++ /dev/null @@ -1,17 +0,0 @@ -func AuctionEnd() { - if std.GetHeight() < auctionEndBlock { - panic("Auction hasn't ended") - } - - if ended { - panic("Auction has ended") - - } - ended = true - - // Send the highest bid to the recipient - banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() - - banker.SendCoins(pkgAddr, receiver, std.Coins{{"ugnot", int64(highestBid)}}) -} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-12.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-12.gno deleted file mode 100644 index 55817537298..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-12.gno +++ /dev/null @@ -1,18 +0,0 @@ -// AuctionEnd() Function Test -func TestAuctionEnd(t *testing.T) { - // Auction is ongoing - shouldPanic(t, AuctionEnd) - - // Auction ends - highestBid = 3 - std.TestSkipHeights(500) - shouldNoPanic(t, AuctionEnd) - shouldEqual(t, ended, true) - - banker := std.GetBanker(std.BankerTypeRealmSend) - shouldEqual(t, banker.GetCoins(receiver).String(), "3ugnot") - - // Auction has already ended - shouldPanic(t, AuctionEnd) - shouldEqual(t, ended, true) -} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno deleted file mode 100644 index 0e5f2d57de9..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-13.gno +++ /dev/null @@ -1,74 +0,0 @@ -// The whole test -func TestFull(t *testing.T) { - bidder01 := testutils.TestAddress("bidder01") // g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw - bidder02 := testutils.TestAddress("bidder02") // g1vf5kger9wgcryh6lta047h6lta047h6lnhe2x2 - - // Variables test - { - shouldEqual(t, highestBidder, "") - shouldEqual(t, receiver, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - shouldEqual(t, auctionEndBlock, 423) - shouldEqual(t, highestBid, 0) - shouldEqual(t, pendingReturns.Size(), 0) - shouldEqual(t, ended, false) - } - - // Send two or more types of coins - { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) - shouldPanic(t, Bid) - } - - // Send less than the highest bid - { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) - shouldPanic(t, Bid) - } - - // Send more than the highest bid - { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldNoPanic(t, Bid) - - shouldEqual(t, pendingReturns.Size(), 0) - shouldEqual(t, highestBid, 1) - shouldEqual(t, highestBidder, "g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw") - } - - // Other participants in the auction - { - - // Send less amount than the current highest bid (current: 1) - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldPanic(t, Bid) - - // Send more amount than the current highest bid (exceeded) - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) - shouldNoPanic(t, Bid) - - shouldEqual(t, highestBid, 2) - shouldEqual(t, highestBidder, "g1vf5kger9wgcryh6lta047h6lta047h6lnhe2x2") - - shouldEqual(t, pendingReturns.Size(), 1) // Return to the existing bidder - shouldEqual(t, pendingReturns.Has("g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw"), true) - } - - // Auction ends - { - std.TestSkipHeights(150) - shouldPanic(t, AuctionEnd) - shouldEqual(t, ended, false) - - std.TestSkipHeights(301) - shouldNoPanic(t, AuctionEnd) - shouldEqual(t, ended, true) - - banker := std.GetBanker(std.BankerTypeRealmSend) - shouldEqual(t, banker.GetCoins(receiver).String(), "2ugnot") - } -} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-2.sol b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-2.sol deleted file mode 100644 index 0040c3ca75f..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-2.sol +++ /dev/null @@ -1,47 +0,0 @@ -// Parameters of the auction. Times are either -// absolute unix timestamps (seconds since 1970-01-01) -// or time periods in seconds. -address payable public beneficiary; -uint public auctionEndTime; - -// Current state of the auction. -address public highestBidder; -uint public highestBid; - -// Allowed withdrawals of previous bids -mapping(address => uint) pendingReturns; - -// Set to true at the end, disallows any change. -// By default initialized to `false`. -bool ended; - -// Events that will be emitted on changes. -event HighestBidIncreased(address bidder, uint amount); -event AuctionEnded(address winner, uint amount); - -// Errors that describe failures. - -// The triple-slash comments are so-called natspec -// comments. They will be shown when the user -// is asked to confirm a transaction or -// when an error is displayed. - -/// The auction has already ended. -error AuctionAlreadyEnded(); -/// There is already a higher or equal bid. -error BidNotHighEnough(uint highestBid); -/// The auction has not ended yet. -error AuctionNotYetEnded(); -/// The function auctionEnd has already been called. -error AuctionEndAlreadyCalled(); - -/// Create a simple auction with `biddingTime` -/// seconds bidding time on behalf of the -/// beneficiary address `beneficiaryAddress`. -constructor( - uint biddingTime, - address payable beneficiaryAddress -) { - beneficiary = beneficiaryAddress; - auctionEndTime = block.timestamp + biddingTime; -} \ No newline at end of file diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-3.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-3.gno deleted file mode 100644 index af8137b4044..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-3.gno +++ /dev/null @@ -1,8 +0,0 @@ -var ( - receiver = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - auctionEndBlock = std.GetHeight() + uint(300) // in blocks - highestBidder std.Address - highestBid = uint(0) - pendingReturns avl.Tree - ended = false -) diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-4.sol b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-4.sol deleted file mode 100644 index 0e3ab6d7e0d..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-4.sol +++ /dev/null @@ -1,32 +0,0 @@ -function bid() external payable { - // No arguments are necessary, all - // information is already part of - // the transaction. The keyword payable - // is required for the function to - // be able to receive Ether. - - // Revert the call if the bidding - // period is over. - if (block.timestamp > auctionEndTime) - revert AuctionAlreadyEnded(); - - // If the bid is not higher, send the - // money back (the revert statement - // will revert all changes in this - // function execution including - // it having received the money). - if (msg.value <= highestBid) - revert BidNotHighEnough(highestBid); - - if (highestBid != 0) { - // Sending back the money by simply using - // highestBidder.send(highestBid) is a security risk - // because it could execute an untrusted contract. - // It is always safer to let the recipients - // withdraw their money themselves. - pendingReturns[highestBidder] += highestBid; - } - highestBidder = msg.sender; - highestBid = msg.value; - emit HighestBidIncreased(msg.sender, msg.value); -} \ No newline at end of file diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno deleted file mode 100644 index 43f0b43b397..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-5.gno +++ /dev/null @@ -1,30 +0,0 @@ -func Bid() { - if std.GetHeight() > auctionEndBlock { - panic("Exceeded auction end block") - } - - sentCoins := std.GetOrigSend() - if len(sentCoins) != 1 { - panic("Send only one type of coin") - } - - sentAmount := uint(sentCoins[0].Amount) - if sentAmount <= highestBid { - panic("Too few coins sent") - } - - // A new bid is higher than the current highest bid - if sentAmount > highestBid { - // If the highest bid is greater than 0, - if highestBid > 0 { - // Need to return the bid amount to the existing highest bidder - // Create an AVL tree and save - pendingReturns.Set(highestBidder.String(), highestBid) - } - - // Update the top bidder address - highestBidder = std.GetOrigCaller() - // Update the top bid amount - highestBid = sentAmount - } -} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno deleted file mode 100644 index b544d0017c4..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-6.gno +++ /dev/null @@ -1,41 +0,0 @@ -// Bid Function Test - Send Coin -func TestBidCoins(t *testing.T) { - // Sending two types of coins - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) - shouldPanic(t, Bid) - - // Sending lower amount than the current highest bid - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) - shouldPanic(t, Bid) - - // Sending more amount than the current highest bid (exceeded) - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldNoPanic(t, Bid) -} - -// Bid Function Test - Bid by two or more people -func TestBidCoins(t *testing.T) { - // bidder01 bidding with 1 coin - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldNoPanic(t, Bid) - shouldEqual(t, highestBid, 1) - shouldEqual(t, highestBidder, bidder01) - shouldEqual(t, pendingReturns.Size(), 0) - - // bidder02 bidding with 1 coin - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldPanic(t, Bid) - - // bidder02 bidding with 2 coins - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) - shouldNoPanic(t, Bid) - shouldEqual(t, highestBid, 2) - shouldEqual(t, highestBidder, bidder02) - shouldEqual(t, pendingReturns.Size(), 1) -} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-7.sol b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-7.sol deleted file mode 100644 index b28ecec1f52..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-7.sol +++ /dev/null @@ -1,20 +0,0 @@ -/// Withdraw a bid that was overbid. -function withdraw() external returns (bool) { - uint amount = pendingReturns[msg.sender]; - if (amount > 0) { - // It is important to set this to zero because the recipient - // can call this function again as part of the receiving call - // before `send` returns. - pendingReturns[msg.sender] = 0; - - // msg.sender is not of type `address payable` and must be - // explicitly converted using `payable(msg.sender)` in order - // use the member function `send()`. - if (!payable(msg.sender).send(amount)) { - // No need to call throw here, just reset the amount owing - pendingReturns[msg.sender] = amount; - return false; - } - } - return true; -} \ No newline at end of file diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno deleted file mode 100644 index 7cb6bbd8d90..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-8.gno +++ /dev/null @@ -1,15 +0,0 @@ -func Withdraw() { - // Query the return amount to non-highest bidders - amount, _ := pendingReturns.Get(std.GetOrigCaller().String()) - - if amount > 0 { - // If there's an amount, reset the amount first, - pendingReturns.Set(std.GetOrigCaller().String(), 0) - - // Return the exceeded amount - banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() - - banker.SendCoins(pkgAddr, std.GetOrigCaller(), std.Coins{{"ugnot", amount.(int64)}}) - } -} diff --git a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno b/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno deleted file mode 100644 index fbc06792ce4..00000000000 --- a/docs/assets/how-to-guides/porting-solidity-to-gno/porting-9.gno +++ /dev/null @@ -1,17 +0,0 @@ -// Withdraw Function Test -func TestWithdraw(t *testing.T) { - // If there's no participants for return - shouldEqual(t, pendingReturns.Size(), 0) - - // If there's participants for return (data generation - returnAddr := bidder01.String() - returnAmount := int64(3) - pendingReturns.Set(returnAddr, returnAmount) - shouldEqual(t, pendingReturns.Size(), 1) - shouldEqual(t, pendingReturns.Has(returnAddr), true) - - banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() - banker.SendCoins(pkgAddr, std.Address(returnAddr), std.Coins{{"ugnot", returnAmount}}) - shouldEqual(t, banker.GetCoins(std.Address(returnAddr)).String(), "3ugnot") -} diff --git a/docs/assets/how-to-guides/simple-contract/counter.gno b/docs/assets/how-to-guides/simple-contract/counter.gno deleted file mode 100644 index 0cc45813bda..00000000000 --- a/docs/assets/how-to-guides/simple-contract/counter.gno +++ /dev/null @@ -1,19 +0,0 @@ -package counter - -import ( - "gno.land/p/demo/ufmt" -) - -var count int - -func Increment() { - count++ -} - -func Decrement() { - count-- -} - -func Render(_ string) string { - return ufmt.Sprintf("Count: %d", count) -} diff --git a/docs/assets/how-to-guides/simple-contract/init.gno b/docs/assets/how-to-guides/simple-contract/init.gno deleted file mode 100644 index 823c394dfa0..00000000000 --- a/docs/assets/how-to-guides/simple-contract/init.gno +++ /dev/null @@ -1,11 +0,0 @@ -package counter - -var count int - -// ... - -func init() { - count = 2 * 10 // arbitrary value -} - -// ... diff --git a/docs/assets/how-to-guides/simple-contract/playground_welcome.png b/docs/assets/how-to-guides/simple-contract/playground_welcome.png deleted file mode 100644 index bc067668538..00000000000 Binary files a/docs/assets/how-to-guides/simple-contract/playground_welcome.png and /dev/null differ diff --git a/docs/assets/how-to-guides/simple-library/playground_welcome.png b/docs/assets/how-to-guides/simple-library/playground_welcome.png deleted file mode 100644 index bc067668538..00000000000 Binary files a/docs/assets/how-to-guides/simple-library/playground_welcome.png and /dev/null differ diff --git a/docs/assets/how-to-guides/simple-library/tapas.gno b/docs/assets/how-to-guides/simple-library/tapas.gno deleted file mode 100644 index c55fceaf3b8..00000000000 --- a/docs/assets/how-to-guides/simple-library/tapas.gno +++ /dev/null @@ -1,37 +0,0 @@ -package tapas - -import "std" - -// List of tapas suggestions -var listOfTapas = []string{ - "Patatas Bravas", - "Gambas al Ajillo", - "Croquetas", - "Tortilla Española", - "Pimientos de Padrón", - "Jamon Serrano", - "Boquerones en Vinagre", - "Calamares a la Romana", - "Pulpo a la Gallega", - "Tostada con Tomate", - "Mejillones en Escabeche", - "Chorizo a la Sidra", - "Cazón en Adobo", - "Banderillas", - "Espárragos a la Parrilla", - "Huevos Rellenos", - "Tuna Empanada", - "Sardinas a la Plancha", -} - -// GetTapaSuggestion randomly selects and returns a tapa suggestion -func GetTapaSuggestion(userInput string) string { - - // Create a random number depending on the block height. - // We get the block height using std.GetHeight(), which is from an imported Gno library, "std" - // Note: this value is not fully random and is easily guessable - randomNumber := int(std.GetHeight()) % len(listOfTapas) - - // Return the random suggestion - return listOfTapas[randomNumber] -} diff --git a/docs/assets/how-to-guides/testing-gno/counter-1.gno b/docs/assets/how-to-guides/testing-gno/counter-1.gno deleted file mode 100644 index 6c2f0d69209..00000000000 --- a/docs/assets/how-to-guides/testing-gno/counter-1.gno +++ /dev/null @@ -1,21 +0,0 @@ -// counter-app/r/counter/counter.gno - -package counter - -import ( - "gno.land/p/demo/ufmt" -) - -var count int - -func Increment() { - count++ -} - -func Decrement() { - count-- -} - -func Render(_ string) string { - return ufmt.Sprintf("Count: %d", count) -} diff --git a/docs/assets/how-to-guides/testing-gno/counter-2.gno b/docs/assets/how-to-guides/testing-gno/counter-2.gno deleted file mode 100644 index 1298432bc03..00000000000 --- a/docs/assets/how-to-guides/testing-gno/counter-2.gno +++ /dev/null @@ -1,51 +0,0 @@ -// counter-app/r/counter/counter_test.gno - -package counter - -import "testing" - -func TestCounter_Increment(t *testing.T) { - // Reset the value - count = 0 - - // Verify the initial value is 0 - if count != 0 { - t.Fatalf("initial value != 0") - } - - // Increment the value - Increment() - - // Verify the initial value is 1 - if count != 1 { - t.Fatalf("initial value != 1") - } -} - -func TestCounter_Decrement(t *testing.T) { - // Reset the value - count = 0 - - // Verify the initial value is 0 - if count != 0 { - t.Fatalf("initial value != 0") - } - - // Decrement the value - Decrement() - - // Verify the initial value is 1 - if count != -1 { - t.Fatalf("initial value != -1") - } -} - -func TestCounter_Render(t *testing.T) { - // Reset the value - count = 0 - - // Verify the Render output - if Render("") != "Count: 0" { - t.Fatalf("invalid Render value") - } -} diff --git a/docs/assets/how-to-guides/testing-gno/package_test.png b/docs/assets/how-to-guides/testing-gno/package_test.png deleted file mode 100644 index 17ea90965fd..00000000000 Binary files a/docs/assets/how-to-guides/testing-gno/package_test.png and /dev/null differ diff --git a/docs/assets/how-to-guides/write-simple-dapp/poll-1.gno b/docs/assets/how-to-guides/write-simple-dapp/poll-1.gno deleted file mode 100644 index 98b0ae4ed7a..00000000000 --- a/docs/assets/how-to-guides/write-simple-dapp/poll-1.gno +++ /dev/null @@ -1,71 +0,0 @@ -package poll - -import ( - "std" - - "gno.land/p/demo/avl" -) - -// Main struct -type Poll struct { - title string - description string - deadline int64 // block height - voters *avl.Tree // addr -> yes / no (bool) -} - -// Getters -func (p Poll) Title() string { - return p.title -} - -func (p Poll) Description() string { - return p.description -} - -func (p Poll) Deadline() int64 { - return p.deadline -} - -func (p Poll) Voters() *avl.Tree { - return p.voters -} - -// Poll instance constructor -func NewPoll(title, description string, deadline int64) *Poll { - return &Poll{ - title: title, - description: description, - deadline: deadline, - voters: avl.NewTree(), - } -} - -// Vote Votes for a user -func (p *Poll) Vote(voter std.Address, vote bool) { - p.Voters().Set(voter.String(), vote) -} - -// HasVoted vote: yes - true, no - false -func (p *Poll) HasVoted(address std.Address) (bool, bool) { - vote, exists := p.Voters().Get(address.String()) - if exists { - return true, vote.(bool) - } - return false, false -} - -// VoteCount Returns the number of yay & nay votes -func (p Poll) VoteCount() (int, int) { - var yay int - - p.Voters().Iterate("", "", func(key string, value interface{}) bool { - vote := value.(bool) - if vote == true { - yay = yay + 1 - } - - return false - }) - return yay, p.Voters().Size() - yay -} diff --git a/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno b/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno deleted file mode 100644 index c7dbaedfbb2..00000000000 --- a/docs/assets/how-to-guides/write-simple-dapp/poll-2.gno +++ /dev/null @@ -1,74 +0,0 @@ -package poll - -import ( - "std" - - "gno.land/p/demo/avl" - "gno.land/p/demo/poll" - "gno.land/p/demo/seqid" - "gno.land/p/demo/ufmt" -) - -// state variables -var ( - polls *avl.Tree // id -> Poll - pollIDCounter seqid.ID -) - -func init() { - polls = avl.NewTree() -} - -// NewPoll - Creates a new Poll instance -func NewPoll(title, description string, deadline int64) string { - // get block height - if deadline <= std.GetHeight() { - panic("deadline has to be in the future") - } - - // Generate int - id := pollIDCounter.Next().String() - p := poll.NewPoll(title, description, deadline) - - // add new poll in avl tree - polls.Set(id, p) - - return ufmt.Sprintf("Successfully created poll #%s!", id) -} - -// Vote - vote for a specific Poll -// yes - true, no - false -func Vote(id string, vote bool) string { - // get txSender - txSender := std.GetOrigCaller() - - // get specific Poll from AVL tree - pollRaw, exists := polls.Get(id) - - if !exists { - panic("poll with specified doesn't exist") - } - - // cast Poll into proper format - poll, _ := pollRaw.(*poll.Poll) - - voted, _ := poll.HasVoted(txSender) - if voted { - panic("you've already voted!") - } - - if poll.Deadline() <= std.GetHeight() { - panic("voting for this poll is closed") - } - - // record vote - poll.Vote(txSender, vote) - - // update Poll in tree - polls.Set(id, poll) - - if vote == true { - return ufmt.Sprintf("Successfully voted YAY for poll #%s!", id) - } - return ufmt.Sprintf("Successfully voted NAY for poll #%s!", id) -} diff --git a/docs/assets/how-to-guides/write-simple-dapp/poll-3.gno b/docs/assets/how-to-guides/write-simple-dapp/poll-3.gno deleted file mode 100644 index 281c209c1ff..00000000000 --- a/docs/assets/how-to-guides/write-simple-dapp/poll-3.gno +++ /dev/null @@ -1,74 +0,0 @@ -func Render(path string) string { - var b bytes.Buffer - - b.WriteString("# Polls!\n\n") - - if polls.Size() == 0 { - b.WriteString("### No active polls currently!") - return b.String() - } - polls.Iterate("", "", func(key string, value interface{}) bool { - - // cast raw data from tree into Poll struct - p := value.(*poll.Poll) - ddl := p.Deadline() - - yay, nay := p.VoteCount() - yayPercent := 0 - nayPercent := 0 - - if yay+nay != 0 { - yayPercent = yay * 100 / (yay + nay) - nayPercent = nay * 100 / (yay + nay) - } - - b.WriteString( - ufmt.Sprintf( - "## Poll #%s: %s\n", - key, // poll ID - p.Title(), - ), - ) - - dropdown := "
\nPoll details
" - - b.WriteString(dropdown + "Description: " + p.Description()) - - b.WriteString( - ufmt.Sprintf("
Voting until block: %d
Current vote count: %d", - p.Deadline(), - p.Voters().Size()), - ) - - b.WriteString( - ufmt.Sprintf("
YAY votes: %d (%d%%)", yay, yayPercent), - ) - b.WriteString( - ufmt.Sprintf("
NAY votes: %d (%d%%)
", nay, nayPercent), - ) - - dropdown = "
\nVote details" - b.WriteString(dropdown) - - p.Voters().Iterate("", "", func(key string, value interface{}) bool { - - voter := key - vote := value.(bool) - - if vote == true { - b.WriteString( - ufmt.Sprintf("
%s voted YAY!", voter), - ) - } else { - b.WriteString( - ufmt.Sprintf("
%s voted NAY!", voter), - ) - } - return false - }) - - b.WriteString("
\n\n") - return false - }) - return b.String() -} diff --git a/docs/assets/reference/standard-library/std-1.gno b/docs/assets/reference/standard-library/std-1.gno deleted file mode 100644 index 5f5a5d86b76..00000000000 --- a/docs/assets/reference/standard-library/std-1.gno +++ /dev/null @@ -1,14 +0,0 @@ -// returns the list of coins owned by the address -GetCoins(addr Address) (dst Coins) - -// sends coins from one address to another -SendCoins(from, to Address, amt Coins) - -// returns the total supply of the coin -TotalCoin(denom string) int64 - -// issues coins to the address -IssueCoin(addr Address, denom string, amount int64) - -// burns coins from the address -RemoveCoin(addr Address, denom string, amount int64) diff --git a/docs/assets/reference/standard-library/std-2.gno b/docs/assets/reference/standard-library/std-2.gno deleted file mode 100644 index 1ef9a0a1af1..00000000000 --- a/docs/assets/reference/standard-library/std-2.gno +++ /dev/null @@ -1,4 +0,0 @@ -type Coin struct { - Denom string `json:"denom"` // the symbol of the coin - Amount int64 `json:"amount"` // the quantity of the coin -} diff --git a/docs/builders/anatomy-of-a-gno-package.md b/docs/builders/anatomy-of-a-gno-package.md new file mode 100644 index 00000000000..683641979a6 --- /dev/null +++ b/docs/builders/anatomy-of-a-gno-package.md @@ -0,0 +1,127 @@ +# Anatomy of a Gno package + +In this tutorial, you will learn to make a simple `Counter` application in +Gno. We will cover the basics of the Gno language which will help you get +started writing smart contracts for gno.land. + +## Language basics + +Let's dive into the `Counter` example. + +First, we need to declare a package name. + +```go +package counter +``` + +A package is an organizational unit of code; it can contain multiple files, and +as mentioned in previous tutorials, it lives on a specific package path once +deployed to the network. + +Next, let us declare a top level variable of type `int`: + +```go +package counter + +var count int +``` + +In Gno, all top-level variables will automatically be persisted to the network's +state after a successful transaction modifying them. Here, you can define +variables that will store your smart contract's data. + +In our case, we have defined a variable which will store the counter's state. + +Next, let's define functions that users will be able to call to change the state +of the counter: + +```go +package counter + +var count int + +func Increment(change int) int { + count += change + return count +} +``` + +The `Increment()` function has a few important features: +- When written with the first letter in uppercase, the function is + exported. This means that calls to this function from outside the `counter` + package are allowed - be it from off-chain clients or from other Gno programs +- It takes an argument of type `int`, called `change`. This is how the caller + will provide a specific number which will be used to increment the `counter` +- Returns the value of `count` after a successful call + +Next, to make our application more user-friendly, we should define a `Render()` +function. This function will help users see the current state of the Counter +application. + +```go gno path=counter.gno run_expr=println(Render("")) +package counter + +import "strconv" + +var count int + +func Increment(change int) int { + count += change + return count +} + +func Render(_ string) string { + return "Current counter value: " + strconv.Itoa(count) +} +``` + +In our case, we can replace the argument string with a `_`, signifying an unused +variable. Then, we can simply return a string telling us the current value of +`count`. For converting `count` to a string, we can import the `strconv` package +from the Gno standard library, as we do when writing Go code. + +:::info +A valid `Render()` function needs to have the following signature: +```go +func Render(path string) string { + ... +} +``` +::: + +## Writing unit tests + +Following best practices, developers should test their Gno applications to avoid +bugs and other problems down the line. + +Let's see how we can write a simple test for the `Increment()` function. + +```go gno path=counter_test.gno depends_on=counter.gno +package counter + +import "testing" + +func TestIncrement(t *testing.T) { + // Check initial value + if count != 0 { + t.Fatalf("Expected 0, got %d", count) + } + + // Call Increment + value := Increment(42) + + // Check result + if value != 42 { + t.Fatalf("Expected 42, got %d", count) + } +} +``` + +By using the `testing` package from the standard library, we can access the +`testing.T` object that exposes methods which can help us terminate tests in specific cases. + +:::info +Common testing patterns found in Go, such as [TDT](https://go.dev/wiki/TableDrivenTests), +can also be used for Gno. We recommend checking out some of the many examples +found online. +::: diff --git a/docs/builders/become-a-gnome.md b/docs/builders/become-a-gnome.md new file mode 100644 index 00000000000..f6fcacb29a9 --- /dev/null +++ b/docs/builders/become-a-gnome.md @@ -0,0 +1,77 @@ +# Becoming a Gnome Contributor + +In gno.land, contributors are critical to everything—helping to build the +project and shape the ecosystem. Becoming a Gnome isn't just about coding or +submitting contributions; it's about joining a collaborative community where you +can hone your skills, share your ideas, innovate and make an impact in ways that +help gno.land grow and self-govern. This guide isn't a standard rulebook but +rather a helpful starting point to navigate your journey in the ecosystem, find +your own way to contribute and your own way to stand out. + +## What Makes a Great Contributor? + +The best contributors are self-starters and explorers. If you see something +that needs improvement, a problem you can solve (or want to solve), or an area +you'd like to explore, dive in! You don't need to wait for permission. Curiosity is +an asset - ask questions, experiment, and push boundaries to find better solutions. + +Openness and implementation of feedback is also key. The most successful +contributors don't just accept feedback; they seek it out. Whether it's fully +documenting issues or pull requests, making the review process smoother benefits +everyone. Incorporating suggestions not only refines your work and helps you +improve, but makes it easier for others to collaborate. Providing details on +your logic or approach to the contribution, and its purpose and usefulness is +equally important. + +Commitment makes a real difference in the goal of standing out as a Gnome. +Following through on tasks and being reliable keeps the momentum going. Quality +matters too as we are aiming for the highest standard for gno.land—thoughtful, +useful, and well-documented work is much easier to integrate and makes a stronger +impact in the ecosystem. Sticking to contributor +[submission guidelines](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md) +helps speed up reviews and get your contributions merged faster. + +## Showcasing Your Work & Engaging with the Community + +Making your contributions visible is equally important as making them. Start +by creating your own [gno.land namespace](../resources/gno-packages.md#package-path-structure) +to establish your identity, a [Hackerspace journey](https://github.com/gnolang/hackerspace/issues?q=sort:updated-desc%20is:issue%20is:open%20label:%22%F0%9F%8C%84%20journey%22) +to document your work, as well as a [Game of Realms](https://github.com/gnolang/game-of-realms) profile. + +From there, you can start building your profile, starting with a +Home realm — your personal realm which can serve as your on-chain profile, +to document work, share updates, and use as a workspace. It doesn't have to be fancy; +it just needs to represent you. Check out some of the existing Home ealm examples +on the [Hall of Fame](https://gno.land/r/leon/hof). + +Sharing progress is key. Whether it's a challenge you overcame, a cool discovery, +or a lesson learned, your updates help others and foster education to support a +stronger community. Getting involved in discussions, presenting ideas, and +collaborating with others will help build your presence and connections. + +## Finding Your Own Way to Contribute + +Not everyone's contributions look the same, and that's a good thing. If your +work isn't purely code-based—like research, conceptual development, or even +sketches—find creative ways to share it. Summaries, diagrams, or even photos +of notebooks can make your work more accessible to the community. If you prefer +asynchronous communication or face language barriers, don't let that stop you - +there are many ways to stay involved and illustrate your contributions. + +Steps to Becoming a Gnome: + +1. **Secure Your Namespace** – This is your unique identity in gno.land. +2. **Set Up Your Home Realm** – A space to organize and showcase your work. +3. **Build a Portfolio** – Document projects, achievements, and ongoing efforts in the Hackerspace repo and Game of Realms repo. +4. **Engage with the Community** – Share updates, join discussions, and collaborate. + +## Stay True to Yourself + +Your unique perspective, approach, and style are the foundation to making gno.land +special. Authenticity is key, but it's also important to ensure your contributions +are clear and accessible to others. By balancing individuality with effective +communication, you can grow as a contributor and leave a lasting impact. + +Becoming a Gnome is a journey of learning, collaboration, and +creativity. By embracing these principles and finding ways to showcase your work, +you can help shape the future of gno.land while staying true to your own path. diff --git a/docs/builders/connect-clients-and-apps.md b/docs/builders/connect-clients-and-apps.md new file mode 100644 index 00000000000..f6c2bf90683 --- /dev/null +++ b/docs/builders/connect-clients-and-apps.md @@ -0,0 +1,26 @@ +# Connecting Clients and Applications to gno.land + +This guide explains how to connect external applications to gno.land networks +using clients in different languages. You'll learn how to use the RPC endpoints +to query the blockchain and submit transactions. + +## Available Clients + +gno.land provides several client libraries to interact with the blockchain: + +- **[gnoclient](https://gnolang.github.io/gno/github.com/gnolang/gno/gno.land/pkg/gnoclient.html)** - The official Go client for connecting to gno.land networks +- **[gno-js-client](https://github.com/gnolang/gno-js-client)** - A JavaScript client for building web applications +- **[tm2-js-client](https://github.com/gnolang/tm2-js-client)** - A lower-level JavaScript client for direct RPC access + +## Understanding gno.land's RPC Interface + +gno.land networks expose several RPC endpoints that allow you to: + +1. **Query blockchain state** - Retrieve account information, package data, and more +2. **Submit transactions** - Send GNOT tokens, call realm functions, and deploy code +3. **Subscribe to events** - Get real-time updates about blockchain activity + +All RPC endpoints for each network can be found in the [Networks documentation](../resources/gnoland-networks.md). + + + diff --git a/docs/builders/deploy-packages.md b/docs/builders/deploy-packages.md new file mode 100644 index 00000000000..244c491f5f1 --- /dev/null +++ b/docs/builders/deploy-packages.md @@ -0,0 +1,156 @@ +# Deploying Gno Packages to a Network + +Once you've developed and tested your Gno packages locally, the next step is +deploying them to a gno.land network. This guide explains how to deploy both +realms and pure packages using `gnokey`. + +## Prerequisites + +Before deploying, you need: + +1. A working version of your package or realm +2. A gno.land account with sufficient GNOT for gas fees +3. The `gnokey` utility installed and configured +4. (Optional) A registered namespace for deploying under your own path + +In this tutorial, you will learn how to deploy Gno code to a gno.land network +via the CLI using `gnokey`. We will be reusing code from a +[previous tutorial](developing-locally/running-testing-gno.md#setup). + +### A word about gas + +For any state-changing call on the gno.land network, which includes code +deployment, users must pay an execution fee, commonly known as a transaction +fee. This mechanism prevents DoS attacks and is integral to most blockchain +networks. + +Transaction fees on the gno.land network are paid with gno.land's native coin, +GNOT, denominated as `ugnot` (micro-GNOT, `1 GNOT = 1_000_000 ugnot`). + +The transaction fee is calculated as `gas-fee * gas-wanted`, where `gas-fee` is +the current price of a unit of gas in `ugnot`, and `gas-wanted` is the total +number of gas units spent for executing the transaction. + +### Getting testnet GNOT + +When working with [remote networks](../resources/gnoland-networks.md), users +need to get testnet `ugnot` manually. + +`ugnot` for development on remote networks can be obtained via the +[Gno Faucet Hub](https://faucet.gno.land). Select your desired network, input a +Gno address to which you want to receive GNOT, complete the captcha and request +tokens. Soon you should have a GNOT balance. + +If you don't have a Gno address, check out +[Creating a key pair](developing-locally/creating-a-keypair.md), or create one +via a third-party web extension wallet, such as Adena. + +## Deploying with `gnokey` + +Consider the following directory structure for our `Counter` realm: + +``` +counter/ + ├─ gno.mod + ├─ counter.gno + ├─ counter_test.gno +``` + +Let's deploy the `Counter` realm to the +[Portal Loop](../resources/gnoland-networks.md#portal-loop) network. For this, +we can use the `gnokey maketx addpkg` subcommand, which executes a package +deployment transaction. + +We need to tell `gnokey` a couple of things: +- `pkgpath`[^1] to which we want to deploy to on the chain, +- `pkgdir` in which the package is found locally, +- `gas-fee` and `gas-wanted` values, +- the `remote` (RPC endpoint) and `chainid` of the Portal Loop network[^2], +- that we want to broadcast the transaction, and +- the key or the address we want to use to deploy the package. + +The full command would look something like the following: +``` +gnokey maketx addpkg \ +-pkgpath "gno.land/r//counter" \ +-pkgdir "." \ +-gas-fee 10000000ugnot \ +-gas-wanted 8000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" \ +MyKey +``` + +To go into more detail: +- Since we're deploying a realm, the pkgpath must start with `r/`. +- You can only deploy code within your own namespace, which is based on your address[^3]. +- `gas-fee` and `gas-wanted` must be set manually. If you run into an `out of gas` +error, try increasing the `gas-wanted` value [^4]. + +After entering your password, you will have successfully deployed the `Counter` +realm to the Portal Loop network: + +``` +OK! +GAS WANTED: 8000000 +GAS USED: 6288988 +HEIGHT: 955 +EVENTS: [] +TX HASH: 11fWJtYXQlyFcHY12HU1ECYs2GPo/e2z/Fdw6I8rwNs= +``` + +## Choosing a Package Path + +When deploying to gno.land, you need to specify a package path. You have two +options: + +1. **Use your registered username** - If you've registered a username, you can deploy under `gno.land/[r|p]/YOUR_USERNAME/...` +2. **Use your address namespace** - Without a username, you can deploy under `gno.land/[r|p]/YOUR_ADDRESS/...` + +For more information on registering usernames and namespace ownership, see the +[Users and Teams documentation](../resources/users-and-teams.md). + +## Registering a Namespace + +For production packages, you'll want your own namespace: + +1. Follow the [Username Registration](../resources/users-and-teams.md#registration-process) instructions +2. Once registered, deploy under `gno.land/[r|p]/YOUR_USERNAME/...` + +This gives you a more human-readable package path and establishes your identity in the ecosystem. + +## Understanding Deployment Parameters + +- `--pkgpath` - The on-chain path where your code will be stored +- `--pkgdir` - The local directory containing your code +- `--deposit` - The amount of GNOT to deposit (typically 100 GNOT) +- `--gas-fee` - The fee per unit of gas (typically 1 GNOT) +- `--gas-wanted` - Maximum gas units for the transaction +- `--remote` - The RPC endpoint for the network +- `--chainid` - The ID of the blockchain network + +For more details on gas fees and optimization strategies, see the [Gas Fees +documentation](../resources/gas-fees.md). + +## Conclusion + +Congratulations! If everything went as expected, you've successfully deployed a +realm to the Portal Loop network. To see it on `gnoweb` for the Portal Loop, +append `r//counter` to https://gno.land in your browser. + +:::info + +Gno code can also be deployed via the web, using the +[Gno Playground](https://play.gno.land). Deploying via the Playground requires +a third-party web extension wallet, such as Adena. + +::: + +[^1]: Read more about package paths [here](../resources/gno-packages.md). +[^2]: Other network configurations can be found [here](../resources/gnoland-networks.md). +[^3]: Address namespaces ([PA namespaces](../resources/gno-packages.md#package-path-structure)) are automatically granted to +users. Users can register a username using the [gno.land user registry](https://gno.land/r/gnoland/users), +which will grant them access to a matching namespace for that specific network. +[^4]: Automatic gas estimation is being worked on for `gnokey`. Follow progress +[here](https://github.com/gnolang/gno/pull/3330). diff --git a/docs/builders/example-minisocial-dapp.md b/docs/builders/example-minisocial-dapp.md new file mode 100644 index 00000000000..cadfd17efea --- /dev/null +++ b/docs/builders/example-minisocial-dapp.md @@ -0,0 +1,405 @@ +# Example `minisocial` dApp + +We will create a MiniSocial [realm](../resources/realms.md), +a minimalist social media application. This tutorial will showcase a full local +development flow for Gno, using all the tools covered in previous tutorials. + +Find the full app on [this link](https://gno.land/r/docs/minisocial/v1). + +## Prerequisites + +See [Local Development with gnodev](./local-dev-with-gnodev.md) for setup instructions. + +## Setup + +Start by creating a folder that will contain your Gno code: + +```sh +mkdir minisocial +cd minisocial +``` + +Next, initialize a `gno.mod` file. This file declares the package path of your +realm and is used by Gno tools. Run the following command to create a `gno.mod` file: + +```sh +gno mod init gno.land/r/example/minisocial +``` + +In this case, we'll be using the `examples` namespace, but you can change this to +the namespace of your liking later. + +Next, in the same folder, start by creating three files: + +```sh +touch types.gno minisocial.gno render.gno +``` + +While all code can be stored in a single file, separating logical units, +such as types, business logic, and rendering can make your realm more readable. + +## Core functionality + +### `types.gno` + +We can use `types.gno` file to store our types and their functionality. We will be +importing some standard library packages, as well as some pure packages directly +from the chain. + +First, let's declare a `Post` struct that will hold all the data of a single post. +We will import two packages: +- `std` - the [Gno standard package](../resources/gno-stdlibs.md) which provides chain-related functionality +- `time` - which allows us to handle time + +[embedmd]:# (../_assets/minisocial/types-1.gno go) +```go +package minisocial + +import ( + "std" // The standard Gno package + "time" // For handling time operations +) + +// Post defines the main data we keep about each post +type Post struct { + text string // Main text body + author std.Address // Address of the post author, provided by the execution context + createdAt time.Time // When the post was created +} +``` + +Standard libraries such as `time` are ported over directly from Go. Check out the +[Go-Gno Compatability](../resources/go-gno-compatibility.md) page for more info. + +### `posts.gno` + +In this file, we will define the main functions for creating, updating, and deleting +posts. Let's start with top level variables - they are the anchor points of our +app, as they are persisted to storage after each transaction: + +[embedmd]:# (../_assets/minisocial/posts-0.gno go) +```go +package minisocial + +var posts []*Post +``` + +The `posts` slice will hold our all newly created posts. + +Next, in the same file, let's create a function to create new posts. This function +will be [exported](https://go.dev/tour/basics/3), meaning it will be callable via +a transaction by anyone. + +[embedmd]:# (../_assets/minisocial/posts-1.gno go /\/\/ CreatePost/ $) +```go +// CreatePost creates a new post +func CreatePost(text string) error { + // If the body of the post is empty, return an error + if text == "" { + return errors.New("empty post text") + } + + // Append the new post to the list + posts = append(posts, &Post{ + text: text, // Set the input text + author: std.PreviousRealm().Address(), // The author of the address is the previous realm, the realm that called this one + createdAt: time.Now(), // Capture the time of the transaction, in this case the block timestamp + }) + + return nil +} +``` + +A few things to note: +- In Gno, returning errors **_does not_** revert any state changes. Follow Go's + best practices: return early in your code and modify state only after you are sure all + security checks in your code have passed. To discard (revert) state changes, + use `panic()`. +- To get the caller of `CreatePost`, we need to import package `std`, the [Gno standard package](../resources/gno-stdlibs.md), +and use `std.PreviousRealm.Address()`. Check out the [realm concept page](../resources/realms.md) +& the [std package](../resources/gno-stdlibs.md) reference page for more info. +- In Gno, `time.Now()` returns the timestamp of the block the transaction was +included in, instead of the system time. + +:::info Lint & format + +The `gno` binary provides tooling which can help you write correct code. +You can use `gno tool lint` and `gno tool fmt` to lint and format your code, +respectively. +::: + +## Rendering + +Let's start building the "front end" of our app. + +One of the core features of Gno is that developers can simply provide a Markdown +view of their realm state directly in Gno, removing the need for using complex +frontend frameworks, languages, and clients. To learn more about this, check out +[Exploring gno.land](../users/explore-with-gnoweb.md). + +The easiest way to develop this part of our Gno app is to run `gnodev`, which +contains a built-in gno.land node, a built-in instance of `gnoweb`, fast hot +reload, and automatic balance premining. Using `gnodev` will allow us to see our +code changes live. + +Let's start by running `gnodev` inside our `minisocial/` folder: + +``` +❯ gnodev +Accounts ┃ I default address imported name=test1 addr=g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 +GnoWeb ┃ I using default package path=gno.land/r/example/minisocial +Proxy ┃ I lazy loading is enabled. packages will be loaded only upon a request via a query or transaction. loader=local/root +Node ┃ I packages paths=[gno.land/r/example/minisocial] +Event ┃ I sending event to clients clients=0 type=NODE_RESET event={} +GnoWeb ┃ I gnoweb started lisn=http://127.0.0.1:8888 +--- READY ┃ I for commands and help, press `h` took=1.689893s +``` + +If we didn't make any errors in our code, we should get the output as presented +above. If not, follow the stack trace and fix any errors that might have showed up. + +Next, we can open the `gnoweb` instance by opening the local listener at +[`127.0.0.1:8888`](http://127.0.0.1:8888). `gnodev` is configured to open +the package path you're working on by default. + +Since a `Render()` function is not defined yet, `gnoweb` will return an error. +Let's start fixing this, in `render.gno`: + +```go +package minisocial + +func Render(_ string) string { + return "# MiniSocial" +} +``` + +`gnodev` will detect changes in your code and automatically reload, and you +should get `MiniSocial` rendered as a Header 1 in `gnoweb` 🎉 + +Let's start by slowly adding more and more functionality: + +[embedmd]:# (../_assets/minisocial/render-0.gno go) +```go +package minisocial + +func Render(_ string) string { + output := "# MiniSocial\n\n" // \n is needed just like in standard Markdown + + // Handle the edge case + if len(posts) == 0 { + output += "No posts.\n" + return output + } + + // Let's append the text of each post to the output + for _, post := range posts { + output += post.text + "\n\n" + } + + return output +} +``` + +We can now use `gnokey` to call the `CreatePost` function and see how our posts +look rendered on `gnoweb`. Let's use the [Docs] page to obtain the `gnokey` command: + +```sh +gnokey maketx call \ +-pkgpath "gno.land/r/example/minisocial" \ +-func "CreatePost" \ +-args "This is my first post" \ +-gas-fee 1000000ugnot -gas-wanted 5000000 \ +-broadcast \ +-chainid "dev" \ +-remote "tcp://127.0.0.1:26657" \ +{MYKEY} +``` + +If the transaction went through, we should see `This is my first post` under the +header. + +We can make this a bit prettier by introducing a custom `String()` method on +the `Post` struct, in `types.gno`: + +[embedmd]:# (../_assets/minisocial/types-2.gno go /\/\/ String/ $) +```go +// String stringifies a Post +func (p Post) String() string { + out := p.text + "\n\n" + + // We can use `ufmt` to format strings, and the built-in time library formatting function + out += ufmt.Sprintf("_by %s on %s_, ", p.author, p.createdAt.Format("02 Jan 2006, 15:04")) + out += "\n\n" + + return out +} +``` + +Here, package `ufmt` is used to provide string formatting functionality. It can +be imported via with `gno.land/p/demo/ufmt`. + +With this, we can expand our `Render()` function in `posts.gno` as follows: + +[embedmd]:# (../_assets/minisocial/render-1.gno go) +```go +package minisocial + +import "gno.land/p/demo/ufmt" // Gno counterpart to `fmt`, for formatting strings + +func Render(_ string) string { + output := "# MiniSocial\n\n" // \n is needed just like in standard Markdown + + // Handle the edge case + if len(posts) == 0 { + output += "No posts.\n" + return output + } + + // Let's append the text of each post to the output + for i, post := range posts { + // Let's append some post metadata + output += ufmt.Sprintf("#### Post #%d\n\n", i) + // Add the stringified post + output += post.String() + // Add a line break for cleaner UI + output += "---\n\n" + } + + return output +} +``` + +Now, try publishing a few more posts to see that the rendering works properly. + +## Testing our code + +Testing is an essential part of developing reliable applications. +Here we will cover a simple test case and then showcase a more advanced approach +using Table-Driven Tests (TDT), a pattern commonly used in Go. + +Let's create a `post_test.gno` file, and add the following code: + +[embedmd]:# (../_assets/minisocial/posts_test-0.gno go) +```go +package minisocial + +import ( + "std" + "strings" + "testing" + + "gno.land/p/demo/testutils" // Provides testing utilities +) + +func TestCreatePostSingle(t *testing.T) { + // Get a test address for alice + aliceAddr := testutils.TestAddress("alice") + // TestSetRealm sets the realm caller, in this case Alice + std.TestSetRealm(std.NewUserRealm(aliceAddr)) + + text1 := "Hello World!" + err := CreatePost(text1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + // Get the rendered page + got := Render("") + + // Content should have the text and alice's address in it + if !(strings.Contains(got, text1) && strings.Contains(got, aliceAddr.String())) { + t.Fatal("expected render to contain text & alice's address") + } +} +``` + +We can add the following test showcasing how TDT works in Gno: + +[embedmd]:# (../_assets/minisocial/posts_test-1.gno go /func TestCreatePostMultiple/ $) +```go +func TestCreatePostMultiple(t *testing.T) { + // Initialize a slice to hold the test posts and their authors + posts := []struct { + text string + author string + }{ + {"Hello World!", "alice"}, + {"This is some new text!", "bob"}, + {"Another post by alice", "alice"}, + {"A post by charlie!", "charlie"}, + } + + for _, p := range posts { + // Set the appropriate caller realm based on the author + authorAddr := testutils.TestAddress(p.author) + std.TestSetRealm(std.NewUserRealm(authorAddr)) + + // Create the post + err := CreatePost(p.text) + if err != nil { + t.Fatalf("expected no error for post '%s', got %v", p.text, err) + } + } + + // Get the rendered page + got := Render("") + + // Check that all posts and their authors are present in the rendered output + for _, p := range posts { + expectedText := p.text + expectedAuthor := testutils.TestAddress(p.author).String() // Get the address for the author + if !(strings.Contains(got, expectedText) && strings.Contains(got, expectedAuthor)) { + t.Fatalf("expected render to contain text '%s' and address '%s'", expectedText, expectedAuthor) + } + } +} +``` + +Running `gno test . -v` in the `minisocial/` folder should show the tests passing: + +```console +❯ gno test . -v +=== RUN TestCreatePostSingle +--- PASS: TestCreatePostSingle (0.00s) +=== RUN TestCreatePostMultiple +--- PASS: TestCreatePostMultiple (0.00s) +ok . 0.87s +``` + +## Conclusion + +Congratulations on completing your first Gno realm! +Now you're equipped with the required knowledge to venture into gno.land. + +Full code of this app can be found on the Portal Loop network, on +[this link](https://gno.land/r/docs/minisocial). + +## Bonus - resolving usernames + +Let's make our MiniSocial app even better by resolving addresses to potential usernames +registered in the [gno.land user registry](https://gno.land/demo/users). + +We can import the `gno.land/r/sys/users` realm which provides user data and use +it to try to resolve the address: + +[embedmd]:# (../_assets/minisocial/types-2-bonus.gno go /\/\/ String/ $) +```go +// String stringifies a Post +func (p Post) String() string { + out := p.text + "\n\n" + + author := p.author.String() + // We can import and use the r/sys/users package to resolve addresses + user, _ := users.ResolveAddress(p.author) + if user != nil { + // RenderLink provides a link that is clickable + // The link goes to the user's profile page + author = user.RenderLink() + } + + out += ufmt.Sprintf("_by %s on %s_\n\n", author, p.createdAt.Format("02 Jan 2006, 15:04")) + return out +} +``` + + diff --git a/docs/builders/local-dev-with-gnodev.md b/docs/builders/local-dev-with-gnodev.md new file mode 100644 index 00000000000..10fb1e24534 --- /dev/null +++ b/docs/builders/local-dev-with-gnodev.md @@ -0,0 +1,211 @@ +# 4.4 Running a local dev node + +## Prerequisites + +- `gnokey` & `gnodev` set up. See [Installation](installation.md). + +## Overview + +In this tutorial, you will learn how to run up a local development node with +`gnodev`. By spinning up a local gno.land +node, users can simulate the blockchain environment locally on their machines, +allowing them to easily see how their code behaves before deploying it to a +remote gno.land network. + +This tutorial will show you how to use gnodev, +a local development solution stack offering a built-in gno.land node with a +hot-reload feature for packages and realms, as well as a built-in instance of [gnoweb](../users/explore-with-gnoweb.md). + +## Primary features + +Apart from providing a built-in gno.land node and a `gnoweb` instance, `gnodev` +also provides an array of other useful features. Let's explore the three most +prominent ones: +1. Automatic package deployment +2. Premining balances +3. Hot reload + +If you're familiar with the features above, jump to the [practical example +section](#practical-example). + +`gnodev` also provides many useful features such as loading genesis transactions, +resolving packages from remote networks, modifying the built-in node parameters, +etc. Check out the full `gnodev` developer guide for more information. + +### 1. Automatic deployment + +`gnodev` automatically deploys your contracts to the built-in node, making +them readily accessible via `gnoweb`. This means that developers do not need to +manually deploy their contracts during local development. + +Packages and realms are deployed with a default Gno address[^1], which can be changed +via the `-deploy-key` flag. + +#### Detecting package paths + +If the current working directory contains a `gno.mod` file, `gnodev` deploys the +package to the `pkgpath` specified inside. + +If no `gno.mod` file is found, `gnodev` searches for a `.gno` file containing a +package name and deploys it under `gno.land/r/dev/`. + +#### Deploying example packages + +In addition to your working directory, `gnodev` automatically deploys all packages +and realms located in the [examples/ folder](https://github.com/gnolang/gno/tree/master/examples) +from the monorepo it was installed from. This makes all packages in the `examples/` +folder available for use during development. `gnodev` also provides the option +to resolve packages from a remote testnet, which can be set via the `-resolver` flag. // XX should we include this here? + +### 2. Premining balances + +`gnodev` automatically detects your Gno keys from the local `gnokey` keybase, and +pre-mines a large amount of testnet GNOT to all of your addresses, which can +then be used for testing applications. + +You can verify the balance of your addresses by pressing `A` when `gnodev` is running: + +``` +Accounts ┃ I (2) known keys + ┃ table= + ┃ │ KeyName Address Balance + ┃ │ test1 g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 10000000000000ugnot + ┃ │ MyKey g1q4q3uegdnq9rsvf3xgxydr3yqd2v6w2tww5920 10000000000000ugnot +``` + +This simplifies development by removing the need to manually acquire testnet GNOT. +This is not the case for remote testnets, where users must obtain testnet GNOT +from faucets, such as the ones found on [faucet.gno.land](https://faucet.gno.land). + +### 3. Hot reload + +`gnodev` watches the current working directory for any changes that happen within +your code, and automatically reloads the built-in node, while trying to replay +previous transactions to maintain the state of your smart contracts between +code changes. + +Directory watching, as well as transaction replaying, can be disabled with the +`-no-watch` and `-no-replay` flags, respectively. + +With the main features of `gnodev` out of the way, let's dive into a practical +example. + +## Practical example + +Let's use the local file structure we set up in the [previous tutorial](running-testing-gno.md#setup): + +``` +counter/ + ├─ gno.mod + ├─ counter.gno +``` + +Let's go into the `counter` folder and run `gnodev`: + +```bash +cd counter +gnodev +``` + +You should receive an output similar to the following: + +```bash +❯ gnodev +Loader ┃ I guessing directory path path=gno.land/r/example/counter dir={your_pwd} +Accounts ┃ I default address imported name=test1 addr=g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 +Node ┃ I packages paths=[gno.land/r/example/counter] +Event ┃ I sending event to clients clients=0 type=NODE_RESET event=&{} +GnoWeb ┃ I gnoweb started lisn=http://127.0.0.1:8888 +--- READY ┃ I for commands and help, press `h` took=1.391020125s +``` + +By opening the `gnoweb` listener address, [`http://localhost:8888`](http://127.0.0.1:8888), +we should see the render of our `counter` realm: + +``` +Current counter value: 0 +``` + +### Modifying `Render()` + +Let's modify the `Render()` function inside `counter.gno` as follows, importing +the `strconv` package: + +```go +func Render(_ string) string { + return "My amazing counter value: " + strconv.Itoa(count) +} +``` + +`gnodev` will automatically detect the change in the file and reload both the node +and `gnoweb`. The render of our realm will then change: + +``` +My amazing counter value: 0 +``` + +### Interacting with the realm + +To interact with our `counter` realm, let's create a simple transaction calling +the `Increment()` function with `gnokey`, using the key we created in the +[previous tutorial](creating-a-keypair.md). Running the following command +in your terminal will execute the transaction: + +``` +gnokey maketx call \ +-pkgpath "gno.land/r/example/counter" \ +-func "Increment" \ +-args "42" \ +-gas-fee 1000000ugnot \ +-gas-wanted 20000000 \ +-broadcast \ +{MYKEY} +``` + +After entering the keypair password, you should get a response similar to this: + +``` +Enter password. +(42 int) + +OK! +GAS WANTED: 20000000 +GAS USED: 126933 +HEIGHT: 203 +EVENTS: [] +TX HASH: k+WuKgPpoAg+EcR2EnzqxeWqUXB4KhOhg3l6zthSy0I= +``` + +Looking at the render of our realm, we'll see that the value of the counter +has increased, as expected: + +``` +My amazing counter value: 42 +``` + +:::info + +The above section showcases a simple `gnokey` command that will execute a +transaction executing `Increment(42)` in the `counter` realm, which lives on the +`gno.land/r/example/counter` package path on the local node. + +A detailed explanation how to use `gnokey` will be provided in an +upcoming tutorial. + +::: + +After running gnodev, you can access several components: + +1. A local version of [gnoweb](../users/explore-with-gnoweb.md) +2. A local blockchain instance for testing +3. The web-based gnodev UI to monitor your node + +## Conclusion + +That's it! 🎉 + +We covered the main features of `gnodev`. Next, we will go onto a full development +example, where we build a minimal social media app. + +[^1]: The default deployer address is `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5`, +a.k.a. `test1` - the mnemonic phrase for this address is publicly known. diff --git a/docs/builders/what-is-gnolang.md b/docs/builders/what-is-gnolang.md new file mode 100644 index 00000000000..bfb8471ac9d --- /dev/null +++ b/docs/builders/what-is-gnolang.md @@ -0,0 +1,88 @@ +# What is Gno? + +[Gno](https://github.com/gnolang/gno) is an interpretation of the widely-used Go +(Golang) programming language for blockchain created by Cosmos co-founder Jae +Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to +Go, so Go programmers can start coding in Gno right away, with a minimal +learning curve. For example, Gno comes with blockchain-specific standard +libraries, but any code that doesn’t use blockchain-specific logic can run in Go +with minimal processing. Libraries that don’t make sense in the blockchain +context, such as network or operating-system access, are not available in +Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so +most of the parsing of the source code is the same. + +Under the hood, the Gno code is parsed into an abstract syntax tree (AST) and +the AST itself is used in the interpreter, rather than bytecode as in many +virtual machines such as Java, Python, or Wasm. This makes even the GnoVM +accessible to any Go programmer. The novel design of the intuitive GnoVM +interpreter allows Gno to freeze and resume the program by persisting and +loading the entire memory state. Gno is deterministic, auto-persisted, and +auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the +programmer doesn’t have to serialize and deserialize objects to persist them +into a database (unlike programming applications with the Cosmos SDK). + +## How Gno Differs from Go + +The composable nature of Go/Gno allows for type-checked interactions between +contracts, making gno.land safer and more powerful, as well as operationally +cheaper and faster. Smart contracts on gno.land are light, simple, more focused, +and easily interoperable—a network of interconnected contracts rather than +siloed monoliths that limit interactions with other contracts. + +## Gno Inherits Go’s Built-in Security Features + +Go supports secure programming through exported/non-exported fields, enabling a +“least-authority” design. It is easy to create objects and APIs that expose only +what should be accessible to callers while hiding what should not be simply by +the capitalization of letters, thus allowing a succinct representation of secure +logic that can be called by multiple users. + +Another major advantage of Go is that the language comes with an ecosystem of +great tooling, like the compiler and third-party tools that statically analyze +code. Gno inherits these advantages from Go directly to create a smart contract +programming language that provides embedding, composability, type-check safety, +and garbage collection, helping developers to write secure code relying on the +compiler, parser, and interpreter to give warning alerts for common mistakes. + +## Gno vs Solidity + +The most widely-adopted smart contract language today is Ethereum’s +EVM-compatible Solidity. With bytecode built from the ground up and Turing +complete, Solidity opened up a world of possibilities for decentralized +applications (dApps) and there are currently more than 10 million contracts +deployed on Ethereum. However, Solidity provides limited tooling and its EVM has +a stack limit and computational inefficiencies. + +Solidity is designed for one purpose only (writing smart contracts) and is bound +by the limitations of the EVM. In addition, developers have to learn several +languages if they want to understand the whole stack or work across different +ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart +contract languages like CosmWasm or Substrate) as every part of the stack is +written in Gno. It’s easy for developers to understand the entire system just by +studying a relatively small code base. + +## Gno Is Essential for the Wider Adoption of Web3 + +Gno makes imports as easy as they are in web2 with runtime-based imports for +seamless dependency flow comprehension, and support for complex structs, beyond +primitive types. Gno is ultimately cost-effective as dependencies are loaded +once, enabling remote function calls as local, and providing automatic and +independent per-realm state persistence. + +Using Gno, developers can rapidly accelerate application development and adopt a +modular structure by reusing and reassembling existing modules without building +from scratch. They can embed one structure inside another in an intuitive way +while preserving localism, and the language specification is simple, +successfully balancing practicality and minimalism. + +The Go language is so well designed that the Gno smart contract system will +become the new gold standard for smart contract development and other blockchain +applications. As a programming language that is universally adopted, secure, +composable, and complete, Gno is essential for the broader adoption of web3 and +its sustainable growth. + +## Getting Started + +Ready to write your first Gno program? Continue to: +- [Anatomy of a Gno Package](./anatomy-of-a-gno-package.md) - Learn the basic structure +- [Local Development with gnodev](./local-dev-with-gnodev.md) - Set up your local environment diff --git a/docs/concepts/concepts.md b/docs/concepts/concepts.md deleted file mode 100644 index 452627747b5..00000000000 --- a/docs/concepts/concepts.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -id: concepts ---- - -# Concepts - -Welcome to the **Concepts** section for Gno. This section outlines the most important -concepts related to Gno & gno.land. diff --git a/docs/concepts/from-go-to-gno.md b/docs/concepts/from-go-to-gno.md deleted file mode 100644 index 41cccc6e971..00000000000 --- a/docs/concepts/from-go-to-gno.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -id: from-go-to-gno ---- - -# From Go to Gno - -## Runtime comparison - -TODO - -## Side-by-side comparison - -TODO - -## Lifecycle comparison - -``` - _____ _____ - / ___/__ / ___/__ ___ - / (_ / _ \ / (_ / _ \/ _ \ - \___/\___/ \___/_//_/\___/ -+----------------+ +----------------+ | -| | | | | -| Write app |---------->| Write contract | | -| | | | | -+----------------+ +----------------+ | -+----------------+ +----------------+ | Develop as usual -| | | | | -| Test locally |---------->| Test locally | | -| | | | | -+----------------+ +----------------+ v - -+----------------+ +----------------+ | -| | | | | -| Compile |-----+ | | | -| | | | | | -+----------------+ | | | | -| | | | | | -| Rent hosting |-----+ | | | -| | | | | | -+----------------+ +---->|Publish on chain| | Deploy -| | | | | | -| Upload binary |-----+ | | | -| | | | | | -+----------------+ | | | | -| | | | | | -|Setup a database|-----+ | | | -| | | | | -+----------------+ +----------------+ v - -+----------------+ +----------------+ | -| Users can | | | | -| interact with |-----+ | | | -| the server | | | | | -+----------------+ | | | | -| | | | | | -| Monitoring | | | | | -| | | | Users interact | | -+----------------+ | | with the chain | | Run -| Maintain | | | | | -| database |-----+---->| Forever | | -| | | | | | -+----------------+ | | Automatic | | -| | | | persistency | | -| Scalability |-----+ | | | -| | | | | | -+----------------+ | | | | -| Keep paying to | | | | | -|keep the service|-----+ | | | -| up | | | | -+----------------+ +----------------+ v -``` - -## See also - -- [go-gno-compatibility.md](../reference/go-gno-compatibility.md) -- ["go -> gno" presentation by Zack Scholl](https://github.com/gnolang/workshops/tree/main/presentations/2023-06-26--go-to-gno--schollz) diff --git a/docs/concepts/gno-language.md b/docs/concepts/gno-language.md deleted file mode 100644 index b733f37b44f..00000000000 --- a/docs/concepts/gno-language.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -id: gno-language ---- - -# The Gno Language - -Gno (Gnolang) is an interpretation of the widely-used Go (Golang) programming language for blockchain created by Cosmos -co-founder Jae Kwon in 2021 to mark a new era in smart contracting. Gno is almost identical to Go, so Go developers can -quickly start using it, with minimal effort. For example, Gno comes with blockchain-specific standard libraries, but any -code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that could lead to -non-deterministic behaviour when executed by thousands of validators are not available in Gno, such as network access, -or determining system time. Otherwise, Gno loads and uses many standard libraries that power Go, so the experience -writing code feels very similar to Go's. - -Under the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, -rather than bytecode as in many virtual machines such as Java, Python, or Wasm. The design aims to make reading & -understanding the source code of the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM -interpreter allows Gno to freeze and resume the program by persisting and loading the memory state automatically. Gno is -deterministic, auto-persisted, and auto-Merkle-ized, allowing programs to be succinct, as the programmer doesn’t have to -serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK). - -## How Gno Differs from Go - -The composable nature of Go/Gno allows for type-checked interactions between contracts, making gno.land safer and more -powerful, as well as operationally cheaper and faster. Smart contracts on gno.land are light, simple, more focused, and -easily interoperable - they represent a network of interconnected contracts rather than siloed monoliths that limit -interactions with other contracts. - -## Gno Inherits Go’s Built-in Security Features - -Go supports secure programming through exported/non-exported fields, enabling a “least-authority” design. It is easy to -create objects and APIs that expose only what should be accessible to callers while hiding what should not be simply by -the capitalization of letters, thus allowing a succinct representation of secure logic that can be called by multiple -users. - -Another major advantage of Go is that the language comes with an ecosystem of great tooling, like the compiler and -third-party tools that statically analyze code. Gno inherits these advantages from Go directly to create a smart -contract programming language that provides embedding, composability, type-check safety, and garbage collection, helping -developers to write secure code relying on the compiler, parser, and interpreter to give warning alerts for common -mistakes. - -## Gno vs Solidity - -The most widely-adopted smart contract language today is Ethereum’s EVM-compatible Solidity. With bytecode built from -the ground up and Turing complete, Solidity opened up a world of possibilities for decentralized applications (dApps) -and there are currently more than 10 million contracts deployed on Ethereum. However, Solidity provides limited tooling -and its EVM has a stack limit and computational inefficiencies. - -Solidity is designed for one purpose only (writing smart contracts) and is bound by the limitations of the EVM. In -addition, developers have to learn several languages if they want to understand the whole stack or work across different -ecosystems. Gno aspires to exceed Solidity on multiple fronts (and other smart contract languages like CosmWasm or -Substrate) as every part of the stack is written in Go (or Gno!). It’s easy for developers to understand the entire system just -by studying a relatively small code base. - -## Gno Is Essential for the Wider Adoption of Web3 - -Gno makes imports as easy as they are in web2 with runtime-based imports for seamless dependency flow comprehension, and -support for complex structs, beyond primitive types. Gno is ultimately cost-effective as dependencies are loaded once, -enabling remote function calls as local, and providing automatic and independent per-realm state persistence. - -Using Gno, developers can rapidly accelerate application development and adopt a modular structure by reusing and -reassembling existing modules without building from scratch. They can embed one structure inside another in an intuitive -way while preserving localism, and the language specification is simple, successfully balancing practicality and -minimalism. - -Building on top of the excellent design of Go, the aim for Gno programming is to become the new gold standard for smart -contract development, not just in our ecosystem but blockchain as a whole. Combining Go's large success, together with -type safety and composability, Gno aims to kickstart a broader adoption of Web3 and its growth. diff --git a/docs/concepts/gno-modules.md b/docs/concepts/gno-modules.md deleted file mode 100644 index 8ebc56b312e..00000000000 --- a/docs/concepts/gno-modules.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -id: gno-modules ---- - -# Gno Modules - -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? - -`gno.mod` file is very useful for local testing and development. Its primary purposes include: - -- **Working outside of the monorepo**: by adding a `gno.mod` file to your directory, all gno tooling will recognise it and understand the implicit import path of your current directory (marked by the `module` directive in your `gno.mod` file). -- **Local dependency management**: the gno.mod file allows you to manage and download local dependencies effectively when developing Go Modules. -- **Configuration and metadata (WIP)**: while the gno.mod file is currently used for specifying dependencies, it's worth noting that in the future, it might also serve as a container for additional configuration and metadata related to Gno Modules. For more information, see: [issue #498](https://github.com/gnolang/gno/issues/498). - -## Gno Modules and Subdirectories - -It's important to note that Gno Modules do not include subdirectories. Each directory within your project is treated as an individual Gno Module, and each should contain its own gno.mod file, even if it's located within an existing Gno Module directory. - -## Available gno Commands - -The gno command-line tool provides several commands to work with the gno.mod file and manage dependencies in Gno Modules: - -- **gno mod init**: small helper to initialize a new `gno.mod` file. -- **gno mod download**: downloads the dependencies specified in the gno.mod file. This command fetches the required dependencies from chain and ensures they are available for local testing and development. -- **gno mod tidy**: removes any unused dependency and adds any required but not yet listed in the file -- most of the maintenance you'll usually need to do! -- **gno mod why**: explains why the specified package or module is being kept by `gno mod tidy`. - -## Sample `gno.mod` file - -``` -module gno.land/p/demo/sample - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest -) - -``` - -- **`module gno.land/p/demo/sample`**: specifies the package/realm import path. -- **`require` Block**: lists the required dependencies. Here using the latest available versions of "gno.land/p/demo/avl" and "gno.land/p/demo/testutils". These dependencies should be specified with the version "v0.0.0-latest" since on-chain packages currently do not support versioning. diff --git a/docs/concepts/gno-test.md b/docs/concepts/gno-test.md deleted file mode 100644 index 754dfad43c8..00000000000 --- a/docs/concepts/gno-test.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -id: gno-test ---- - -# Gno Test - -There are two methods for testing a realm or package during the development phase: - -1. Calling the realm/package after deploying it on a local network (or testnet). -2. Using the `test` option within the [`gno`](../gno-tooling/cli/gno.md) CLI. - -While the first method is recommended for its accuracy and similarity to the actual deployment environment, it is more efficient to initially utilize the second method for composing test cases and then proceed to the first method if no errors are detected. - -This section will teach you how to use the second method. - -Writing test cases in Gno is similar to that of Go, with general rules as the following: - -* Test file naming conventions must be adhered to (ex: `xxx_test.gno`). -* Test functions must start with `Test`. -* The `t *testing.T` argument must be included in each test function. - * The `testing` package must be imported. -* Tests must be run with the `gno test` command. - -Let's write a sample code and test it. - -```go -// contract.gno - -package demo - -func Hello(name string) string { - return "Hello " + name + "!" -} -``` - -This is a simple code that returns the string-typed argument in a specific format. - -Next, we'll write a test case that looks like the following: - -```go -// contract_test.gno - -package demo - -import "testing" - -func TestHello(t *testing.T) { - { - got := Hello("People") - expected := "Hello People!" - if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) - } - } - { - got := Hello("") - expected := "Hello People!" - if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) - } - } -} -``` - -Two conditions exist in the test case above. - -1. "Hello People!" should be returned when calling `Hello("People")`. -2. "Hello People!" should be returned when calling `Hello("")`. - -Upon examination of our realm code and the associated test results, the initial condition exhibited the desired behavior; however, an error was identified in the second condition. -Despite the expected outcome of "Hello" being returned, the test case incorrectly specified that the expected output should be "Hello People!" instead. - -Replacing the second test case with the following will successfully fix the issue and allow the test to pass. - -```go - { - got := Hello("") - expected := "Hello !" - if expected != got { - t.Fatalf("expected %q, got %q.", expected, got) - } - } -``` - -## Blockchain context in tests -Running `gno test` executes files within the directory that end with `_test.gno` and `_filetest.gno`. -Internally, a GnoVM instance is initialized to run the test, and, at that moment, -a blockchain-related context is injected into the GnoVM. Utilizing this context, the transaction sender, -coins, block height, etc. can be mocked. - -For detailed information on these functions, refer to their [reference page](../reference/stdlibs/std/testing.md). diff --git a/docs/concepts/gnovm.md b/docs/concepts/gnovm.md deleted file mode 100644 index 13e55defb71..00000000000 --- a/docs/concepts/gnovm.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -id: gnovm ---- - -# GnoVM - -GnoVM is a virtual machine that interprets Gno, a custom version of Go optimized for blockchains, featuring automatic state management, full determinism, and idiomatic Go. -It works with Tendermint2 and enables smarter, more modular, and transparent appchains with embedded smart-contracts. -It can be adapted for use in TendermintCore, forks, and non-Cosmos blockchains. - -Read the ["Intro to Gnoland"](https://gno.land/r/gnoland/blog:p/intro) blogpost. - -This folder focuses on the VM, language, stdlibs, tests, and tools, independent of the blockchain. -This enables non-web3 developers to contribute without requiring an understanding of the broader context. - -## Language Features - -* Like interpreted Go, but more ambitious. -* Completely deterministic, for complete accountability. -* Transactional persistence across data realms. -* Designed for concurrent blockchain smart contracts systems. - -## Getting started - -Install [`gno`](../getting-started/local-setup/local-setup.md) and refer to the [`examples`](https://github.com/gnolang/gno/tree/master/examples) folder to start developing contracts. - -Check the [Makefile](https://github.com/gnolang/gno/blob/master/gnovm/Makefile) to enhance GnoVM, Gno, and stdlibs. diff --git a/docs/concepts/namespaces.md b/docs/concepts/namespaces.md deleted file mode 100644 index c7f03ec1f0a..00000000000 --- a/docs/concepts/namespaces.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -id: namespaces ---- - -# Namespaces - -Namespaces provide users with the exclusive capability to publish contracts under their designated namespaces, -similar to GitHub's user and organization model. - -:::warning Not enabled - -This feature isn't enabled by default on the portal loop chain and is currently available only on test4.gno.land. - -::: - -# Package Path - -A package path is a unique identifier for each package/realm. It specifies the location of the package source -code which helps differentiate it from others. You can use a package path to: - -- Call a specific function from a package/realm. (e.g using `gnokey maketx call`) -- Import it in other packages/realms. - -Here's a breakdown of the structure of a package path: - -- Domain: The domain of the blockchain where the package is deployed. Currently, only `gno.land/` is supported. -- Type: Defines the type of package. - - `p/`: [Package](packages.md) - - `r/`: [Realm](realms.md) -- Namespace: A namespace can be included after the type (e.g., user or organization name). Namespaces are a - way to group related packages or realms, but currently ownership cannot be claimed. (see - [Issue#1107](https://github.com/gnolang/gno/issues/1107) for more info) -- Remaining Path: The remaining part of the path. - - Can only contain alphanumeric characters (letters and numbers) and underscores. - - No special characters allowed (except underscore). - - Cannot consist solely of underscores. It must have at least one allowed alphanumeric character. - - Cannot start with a number. It should begin with a letter. - - Cannot end with a trailing slash (`/`). - -Examples: - -- `gno.land/p/demo/avl`: This signifies a package named `avl` within the `demo` namespace. -- `gno.land/r/gnoland/home`: This signifies a realm named `home` within the `gnoland` namespace. - -## Registration Process - -The registration process is contract-based. The `AddPkg` command references -`sys/users` for filtering, which in turn is based on `r/demo/users`. - -When `sys/users` is enabled, you need to register a name using `r/demo/users`. You can call the -`r/demo/users.Register` function to register the name for the caller's address. - -> ex: `test1` user registering as `patrick` -```bash -$ gnokey maketx call -pkgpath gno.land/r/demo/users \ - -func Register \ - -gas-fee 1000000ugnot -gas-wanted 2000000 \ - -broadcast \ - -chainid=test4 \ - -send=20000000ugnot \ - -args '' \ - -args 'patrick' \ - -args 'My Profile Quote' test1 -``` - -:::note Chain-ID - -Do not forget to update chain id, adequate to the network you're interacting with - -::: - - -After successful registration, you can add a package under the registered namespace. - -## Anonymous Namespace - -gno.land offers the ability to add a package without having a registered namespace. -You can do this by using your own address as a namespace. This is formatted as `{p,r}/{std.Address}/**`. - -> ex: with `test1` user adding a package `microblog` using his own address as namespace -```bash -$ gnokey maketx addpkg \ - --pkgpath "gno.land/r/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5/microblog" \ - --pkgdir "examples/gno.land/p/demo/microblog" \ - --deposit 100000000ugnot \ - --gas-fee 1000000ugnot \ - --gas-wanted 2000000 \ - --broadcast \ - --chainid test4 \ - test1 -``` diff --git a/docs/concepts/packages.md b/docs/concepts/packages.md deleted file mode 100644 index dfc49a87caf..00000000000 --- a/docs/concepts/packages.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -id: packages ---- - -# Packages - -Packages aim to encompass functionalities that are more closely aligned with the characteristics and capabilities of realms, as opposed to standard libraries. As opposed to realms, they are stateless. - -The full list of pre-deployed available packages can be found under the [demo package](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo). Below are some of the most commonly used packages. - -## `avl` - -In Go, the classic key/value data type is represented by the `map` construct. However, while Gno also supports the use of `map`, it is not a viable option as it lacks determinism due to its non-sequential nature. - -To address this issue, Gno implements the [AVL Tree](https://en.wikipedia.org/wiki/AVL_tree) (Adelson-Velsky-Landis Tree) as a solution. The AVL Tree is a self-balancing binary search tree. - -The `avl` package comprises a set of functions that can manipulate the leaves and nodes of the AVL Tree. - -## `grc20` - -Gno includes an implementation of the `erc20` fungible token standard referred to as `grc20`. The interfaces of `grc20` are as follows: - -[embedmd]:# (../assets/explanation/packages/pkg-1.gno go) -```go -func TotalSupply() uint64 -func BalanceOf(account std.Address) uint64 -func Transfer(to std.Address, amount uint64) -func Approve(spender std.Address, amount uint64) -func TransferFrom(from, to std.Address, amount uint64) -func Allowance(owner, spender std.Address) uint64 -``` - -The role of each function is as follows: - -* `TotalSupply`: Returns the total supply of the token. -* `BalanceOf`: Returns the balance of tokens of an account. -* `Transfer`: Transfers specific `amount` of tokens from the `caller` of the function to the `to` address. -* `Approve`: Grants the `spender`(also referred to as `operator`) with the ability to send specific `amount` of the `caller`'s (also referred to as `owner`) tokens on behalf of the `caller`. -* `TransferFrom`: Can be called by the `operator` to send specific `amount` of `owner`'s tokens from the `owner`'s address to the `to` address. -* `Allowance`: Returns the number of tokens approved to the `spender` by the `owner`. - -Two types of contracts exist in`grc20`: - -1. `Banker` - - Implements the token factory with `Helper` functions. - - The underlying struct should not be exposed to users. However, it can return a typecasted `Token` object using the `Token()` method. -2. `Token` - - Implements the `GRC20` interface. - - The underlying struct can be exposed to users. Created with the `Token()` method of `Banker`. - -## `grc721` - -Gno includes an implementation of the `erc721` non-fungible token standard referred to as `grc721`. The interfaces of `grc721` are as follows: - -[embedmd]:# (../assets/explanation/packages/pkg-2.gno go) -```go -// functions that work similarly to those of grc20 -func BalanceOf(owner std.Address) (uint64, error) -func Approve(approved std.Address, tid TokenID) error -func TransferFrom(from, to std.Address, tid TokenID) error - -// functions unique to grc721 -func OwnerOf(tid TokenID) (std.Address, error) -func SafeTransferFrom(from, to std.Address, tid TokenID) error -func SetApprovalForAll(operator std.Address, approved bool) error -func GetApproved(tid TokenID) (std.Address, error) -func IsApprovedForAll(owner, operator std.Address) bool -``` - -`grc721` contains a new set of functions: - -* `OwnerOf`: Returns the `owner`'s address of a token specified by its `TokenID`. -* `SafeTransferFrom`: Equivalent to the `TransferFrom` function of `grc20`. - * The `Safe` prefix indicates that the function runs a check to ensure that the `to` address is a valid address that can receive tokens. - * As you can see from the [code](https://github.com/gnolang/gno/blob/master/examples/gno.land/p/demo/grc/grc721/basic_nft.gno#L341), the concept of `Safe` has yet to be implemented. -* `SetApprovalForAll`: Approves all tokens owned by the `owner` to an `operator`. - * You may not set multiple `operator`s. -* `GetApproved`: Returns the `address` of the `operator` for a token, specified with its `ID`. -* `IsApprovedForAll`: Returns if all NFTs of the `owner` have been approved to the `operator`. - -## `testutils` - -The `testutils` package contains a set of functions that comes in handy when testing realms. The sample function below is the commonly used `TestAddress` function that generates a random address. - -[embedmd]:# (../assets/explanation/packages/pkg-3.gno go) -```go -func TestAddress(name string) std.Address { - if len(name) > std.RawAddressSize { - panic("address name cannot be greater than std.AddressSize bytes") - } - addr := std.RawAddress{} - // TODO: use strings.RepeatString or similar. - // NOTE: I miss python's "".Join(). - blanks := "____________________" - copy(addr[:], []byte(blanks)) - copy(addr[:], []byte(name)) - return std.Address(std.EncodeBech32("g", addr)) -} -``` - -The code takes the `name` as the input and creates a random address. Below is a list of examples where it's used in the test case of the `foo20` realm. - -[embedmd]:# (../assets/explanation/packages/pkg-4.gno go) -```go -admin := users.AddressOrName("g1tntwtvzrkt2gex69f0pttan0fp05zmeg5yykv8") -test2 := users.AddressOrName(testutils.TestAddress("test2")) -recv := users.AddressOrName(testutils.TestAddress("recv")) -normal := users.AddressOrName(testutils.TestAddress("normal")) -owner := users.AddressOrName(testutils.TestAddress("owner")) -spender := users.AddressOrName(testutils.TestAddress("spender")) -recv2 := users.AddressOrName(testutils.TestAddress("recv2")) -mibu := users.AddressOrName(testutils.TestAddress("mint_burn")) -``` diff --git a/docs/concepts/portal-loop.md b/docs/concepts/portal-loop.md deleted file mode 100644 index adc341e3ae4..00000000000 --- a/docs/concepts/portal-loop.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -id: portal-loop ---- - -# Portal Loop - -Portal Loop is an always-up-to-date staging testnet that allows for using -the latest version of Gno, gno.land, and TM2. By utilizing the power of Docker -& the [tx-archive](https://github.com/gnolang/tx-archive) tool, the Portal Loop can run the latest code from the -master branch on the [Gno monorepo](https://github.com/gnolang/gno), -while preserving most/all of the previous transaction data. - -The Portal Loop allows for quick iteration on the latest version of Gno - without -having to make a hard/soft fork. - -Below is a diagram demonstrating how the Portal Loop works: -``` - +----------------------------------+ - | Portal Loop running | < ----+ - +----------------------------------+ | - | | - | | - v | - +----------------------------------+ | - | Detect changes in 'master' | | - +----------------------------------+ | - | | - | | - v | - +----------------------------------+ | - | Archive transaction data & state | | - +----------------------------------+ | - | | - | | - v | - +----------------------------------+ | - | Load changes from 'master' | | - +----------------------------------+ | - | | - | | - v | - +----------------------------------+ | - | Replay transaction data | ------+ - +----------------------------------+ -``` - -Specifically, Portal Loop behaves like a normal network until a change is detected -in the `master` branch in the Gno monorepo. At this point, Portal Loop archives -on-chain data using the [tx-archive](https://github.com/gnolang/tx-archive) -tool, saving all transactions that happened on it thus far. - -It then pulls the latest changes from the `master` branch, and inserts all -previously archived transactions into the genesis of the newly deployed chain. -After genesis has been replayed, the chain continues working as normal. - -## Using the Portal Loop - -The Portal Loop deployment can be found at [gno.land](https://gno.land), while -the exposed RPC endpoints can be found on `https://rpc.gno.land:443`. The RPC endpoint -list can be found in the [reference section](../reference/rpc-endpoints.md). - -### A warning note - -While allowing for quick iteration on the most up-to-date software, the Portal Loop -has some drawbacks: -- If a breaking change happens on `master`, transactions that used the previous version of -Gno will fail to be replayed, meaning **data will be lost**. -- Since transactions are archived and replayed during genesis, -block height & timestamp cannot be relied upon. - -### Deploying to the Portal Loop - -There are two ways to deploy code to the Portal Loop: - -1. *automatic* - all packages in found in the `examples/gno.land/{p,r}/` directory in the [Gno monorepo](https://github.com/gnolang/gno) get added to the - new genesis each cycle, -2. *permissionless* - this includes replayed transactions with `addpkg`, and - new transactions you can issue with `gnokey maketx addpkg`. - -Since the packages in `examples/gno.land/{p,r}` are deployed first, -permissionless deployments get superseded when packages with identical `pkgpath` -get merged into `examples/`. - -The above mechanism is also how the `examples/` on the Portal Loop -get collaboratively iterated upon, which is its main mission. - diff --git a/docs/concepts/proof-of-contribution.md b/docs/concepts/proof-of-contribution.md deleted file mode 100644 index c855b3db8ed..00000000000 --- a/docs/concepts/proof-of-contribution.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -id: proof-of-contribution ---- - -# Proof of Contribution - -The gno.land chain utilizes a reputation-based consensus mechanism instead of proof-of-stake. -This mechanism emphasizes values and expertise to carry out important tasks like choosing validators and allocating rewards. - -Meta issue: [#918](https://github.com/gnolang/gno/issues/918). - -Presentation: https://github.com/gnolang/workshops/tree/main/presentations/2023-06-06--buidl-asia--manfred. - -## Main Concepts - -- Validator set determined by `worxDAO`, a DAO serving as the authority. -- Governance and distribution managed through smart contracts. -- Chain monitors contract changes (e.g., `valset`) to configure `Tendermint2`. -- No staking involved in consensus. -- Chain fees distributed to contributors and validators, not stakers. -- Chain fees accumulated in a contract-managed bucket for efficient distribution. -- Validators likely have equal power (1). -- Validators do not vote like in PoS, but may participate in dedicated governance topics, maybe. - -## High-level schema - -``` - ____ ____ ____ __ _ __ __ _ - / __ \_________ ____ / __/ ____ / __/ _________ ____ / /______(_) /_ __ __/ /_(_)___ ____ _____ - / /_/ / ___/ __ \/ __ \/ /_ / __ \/ /_ / ___/ __ \/ __ \/ __/ ___/ / __ \/ / / / __/ / __ \/ __ \/ ___/ - / ____/ / / /_/ / /_/ / __/ / /_/ / __/ / /__/ /_/ / / / / /_/ / / / /_/ / /_/ / /_/ / /_/ / / / (__ ) - /_/ /_/ \____/\____/_/ \____/_/ \___/\____/_/ /_/\__/_/ /_/_.___/\__,_/\__/_/\____/_/ /_/____/ - - +---------------------------------------------------------------+ +------------------------------------+ - | gno.land/{p,r} contracts | | gno.land | - | | | | - | +-----------------------------+ +---------------------+ | | | - | | | | r/sys/validators | | | | - | | | +->| |--+------+ | +-------------+ | - | | worxDAO | | | validator set | | | | | | | - | | |--+ +---------------------+ | +-------+->| Gno SDK |----------+ | - | | the "Contributors DAO" | | | r/sys/config | | | | | | | | - | | | +->| |--+------+ | +-------------+ | | - | | | | chain configuration | | | | | | - | +-----------------------------+ +---------------------+ | | | | | - | | +---------------------+ | | v v | - | v | r/sys/rewards | | | +-------------+ +-------------+ | - | +----------------------+ | | | | | | | | | - | | Evaluation DAO | | distribute rewards | | | | TM2 |-->| GnoVM | | - | | | | to contributors and | | | | | | | | - | | Qualification system | | validators | | | +-------------+ +-------------+ | - | | to distribute ^worx | | +------+ | | | | | | - | +----------------------+ | |Bucket|<- - -|- + -chain fees -|- - - - - | | - | +-------+------+------+ | | | | - +---------------------------------------------------------------+ +---------------------------+--------+ - ^ | - | | - +---------------user TXs can publish and call contracts--------------------+ -``` - -## Components - -### `gno.land` - -The main blockchain powered by the `TM2` engine. It offers permissionless smart -contracts with the `GnoVM` and can self-configure from contracts using the -`GnoSDK`. - -### `worxDAO` - -The governance entity consisting of contributors, responsible for governing the -`r/sys` realms, including `validators` and `config`. - -Meta issue: [#872](https://github.com/gnolang/gno/issues/872). - -### `r/sys/validators` - -A realm (smart contract) that enables the `worxDAO` to update the validator set. -Similar to a PoA system, the authority is decentralized in a DAO. - -Additionally, this contract is queried by `gno.land` to configure `TM2` when -changes are made to the validator set. - -### `r/sys/config` - -A governance-backed smart contract that allows for chain configuration through -governance. - -It helps prevent unnecessary upgrade campaigns for minor updates. - -### Evaluation DAO - -The system employed by the `worxDAO` to incentivize contributions with `^worx` points. - - +---------------1. propose a contribution-------------+ - | v - +--------------+ +----------------+ - | |--------3. improve, negotiate------->| | - | contributor | | Evaluation DAO | - | |<-------4. distribute ^worx----------| | - +--------------+ +----------------+ - ^ | - +---------------2. review, challenge------------------+ diff --git a/docs/concepts/realms.md b/docs/concepts/realms.md deleted file mode 100644 index c4d68636c90..00000000000 --- a/docs/concepts/realms.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -id: realms ---- - -# Realms - -A realm refers to a specific instance of a smart contract that can be written -in [Gno](./gno-language.md). The potentials of realms are endless - you can create virtually any -application in your mind with built-in composability, -transparency, and censorship resistance. Here are some ideas of what you can build with realms: - -* Self-custodial financial exchanges (decentralized exchanges). -* Lending platforms with better rates. -* Transparent insurance systems. -* Fair and accessible voting systems. -* Logistics and supply chain networks. - -## Packages vs Realms - -#### [**Pure Packages**](https://github.com/gnolang/gno/tree/master/examples/gno.land/p) - -* A unit that contains functionalities and utilities that can be used in realms. -* Packages are stateless. -* The default import path is `gno.land/p/~~~`. -* Can be imported to other realms or packages. -* Cannot import realms. - -#### [**Realms**](https://github.com/gnolang/gno/tree/master/examples/gno.land/r) - -* Smart contracts in Gno. -* Realms are stateful. -* Realms can own assets (tokens). -* The default import path is `gno.land/r/~~~`. -* Realms can implement `Render(path string) string` to simplify dapp frontend development by allowing users to request - markdown renderings from validators and full nodes without a transaction. - -A notable feature of realms is the `Render()` function. - -```go -package demo - -func Render(path string) string { - return "# Hello Gno!" -} -``` - -Upon calling the realm above, `# Hello Gno!` is printed with a string-typed `path` declared in an argument. It should be -noted that while the `path` argument included in the sample code is not utilized, it serves the purpose of -distinguishing the path during the rendering process. diff --git a/docs/concepts/stdlibs/banker.md b/docs/concepts/stdlibs/banker.md deleted file mode 100644 index 873fac7c418..00000000000 --- a/docs/concepts/stdlibs/banker.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -id: banker ---- - -# Banker - -The Banker's main purpose is to handle balance changes of [native coins](./coin.md) within Gno chains. This includes issuance, transfers, and burning of coins. - -The Banker module can be cast into 4 subtypes of bankers that expose different functionalities and safety features within your packages and realms. - -### Banker Types - -1. `BankerTypeReadonly` - read-only access to coin balances -2. `BankerTypeOrigSend` - full access to coins sent with the transaction that called the banker -3. `BankerTypeRealmSend` - full access to coins that the realm itself owns, including the ones sent with the transaction -4. `BankerTypeRealmIssue` - able to issue new coins - -The Banker API can be found under the `std` package [reference](../../reference/stdlibs/std/banker.md). diff --git a/docs/concepts/stdlibs/coin.md b/docs/concepts/stdlibs/coin.md deleted file mode 100644 index 46c7c519f7c..00000000000 --- a/docs/concepts/stdlibs/coin.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -id: coin ---- - -# Coin - -A Coin is a native Gno type that has a denomination and an amount. Coins can be issued by the [Banker](banker.md). - -A coin is defined by the following: - -```go -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} -``` - -`Denom` is the denomination of the coin, i.e. `ugnot`, and `Amount` is a non-negative -amount of the coin. - -Multiple coins can be bundled together into a `Coins` slice: - -```go -type Coins []Coin -``` - -This slice behaves like a mathematical set - it cannot contain duplicate `Coin` instances. - -The `Coins` slice can be included in a transaction made by a user addresses or a realm. -Coins in this set are then available for access by specific types of Bankers, -which can manipulate them depending on access rights. - -Read more about coins in the [Effective Gno](../effective-gno.md#coins) section. - -The Coin(s) API can be found in under the `std` package [reference](../../reference/stdlibs/std/coin.md). diff --git a/docs/concepts/stdlibs/events.md b/docs/concepts/stdlibs/events.md deleted file mode 100644 index 78da28ce083..00000000000 --- a/docs/concepts/stdlibs/events.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -id: events ---- - -# Gno Events - -## Overview - -Events in Gno are a fundamental aspect of interacting with and monitoring -on-chain applications. They serve as a bridge between the on-chain environment -and off-chain services, making it simpler for developers, analytics tools, and -monitoring services to track and respond to activities happening in gno.land. - -Gno events are pieces of data that log specific activities or changes occurring -within the state of an on-chain app. These activities are user-defined; they might -be token transfers, changes in ownership, updates in user profiles, and more. -Each event is recorded in the ABCI results of each block, ensuring that action -that happened is verifiable and accessible to off-chain services. - -## Emitting Events - -To emit an event, you can use the `Emit()` function from the `std` package -provided in the Gno standard library. The `Emit()` function takes in a string -representing the type of event, and an even number of arguments after representing -`key:value` pairs. - -Read more about events & `Emit()` in -[Effective Gno](../effective-gno.md#emit-gno-events-to-make-life-off-chain-easier), -and the `Emit()` reference [here](../../reference/stdlibs/std/chain.md#emit). - -## Data contained in a Gno Event - -An event contained in an ABCI response of a block will include the following -data: - -``` json -{ - "@type": "/tm.gnoEvent", // TM2 type - "type": "OwnershipChange", // Type/name of event defined in Gno - "pkg_path": "gno.land/r/demo/example", // Path of the emitter - "func": "ChangeOwner", // Gno function that emitted the event - "attrs": [ // Slice of key:value pairs emitted - { - "key": "oldOwner", - "value": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" - }, - { - "key": "newOwner", - "value": "g1zzqd6phlfx0a809vhmykg5c6m44ap9756s7cjj" - } - ] -} -``` - -You can fetch the ABCI response of a specific block by using the `/block_results` -RPC endpoint. - diff --git a/docs/concepts/stdlibs/gnopher-hole.md b/docs/concepts/stdlibs/gnopher-hole.md deleted file mode 100644 index c1e52cde8a3..00000000000 --- a/docs/concepts/stdlibs/gnopher-hole.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -id: gnopher-hole-stdlib ---- - -# Gnopher Hole - -## 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, i.e. `std.AssertOriginCall` - or `std.CurrentRealm`. -2. It relies on `unsafe`, or other features which are not planned to be - available in the GnoVM, i.e. `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, i.e. `sha256.Sum256`. - -Native bindings are made to be a special feature which can be -help 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), i.e. `crypto/sha256/sha256.gno`. -2. The Go function definition, which must be a top-level function with the same - name and signature, i.e. `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 by executing `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 (i.e. `func sum256(b []byte)`) must have their - Go counterpart prefixed with `X_` in order to make the functions exported (i.e. - `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, i.e. `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 (i.e. the `package` clause). - For example, 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, such as `import gno "github.com/gnolang/gno/gnovm/pkg/gnolang"`. - -## 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`. - -:::note -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](../../reference/go-gno-compatibility.md) accordingly. - - diff --git a/docs/concepts/stdlibs/stdlibs.md b/docs/concepts/stdlibs/stdlibs.md deleted file mode 100644 index 698bbabbd51..00000000000 --- a/docs/concepts/stdlibs/stdlibs.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -id: stdlibs ---- - -# Standard Libraries - -Gno comes with a set of standard libraries which are included to ease development and provide extended functionality to the language. These include: -- standard libraries as we know them in classic Go, i.e. `encoding/binary`, `strings`, `testing`, etc. -- a special `std` package, which contains types, interfaces, and APIs created to handle blockchain-related functionality. - -Standard libraries differ from on-chain packages in terms of their import path structure. -Unlike on-chain [packages](../packages.md), standard libraries do not incorporate a domain-like format at the beginning -of their import path. For example: -- `import "encoding/binary"` refers to a standard library -- `import "gno.land/p/demo/avl"` refers to an on-chain package. - -To see concrete implementation details & API references, see the [reference](../../reference/stdlibs/stdlibs.md) section. - -## Accessing documentation - -Apart from the official documentation you are currently reading, you can also access documentation for the standard -libraries in several other different ways. You can obtain a list of all the available standard libraries 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 -... -``` - -All the packages have automatically generated documentation through the use of the -`gno doc` command, which has similar functionality and features to `go doc`: - -```console -$ gno doc encoding/binary -package binary // import "encoding/binary" - -Package binary implements simple translation between numbers and byte sequences -and encoding and decoding of varints. - -[...] - -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" - -func (littleEndian) AppendUint16(b []byte, v uint16) []byte { - return append(b, - byte(v), - byte(v>>8), - ) -} -``` - -`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`. - -Another alternative is setting your environment variable `GNOROOT` to point to -where you cloned the Gno repository. - -```sh -export GNOROOT=$HOME/gno -``` diff --git a/docs/concepts/tendermint2.md b/docs/concepts/tendermint2.md deleted file mode 100644 index 50bf996247d..00000000000 --- a/docs/concepts/tendermint2.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -id: tendermint2 ---- - -# Tendermint2 - -**Disclaimer: Tendermint2 is currently part of the Gno monorepo for streamlined development.** - -**Once gno.land is on the mainnet, Tendermint2 will operate independently, including for governance, -on https://github.com/tendermint/tendermint2.** - -## Problems - -* Open source is open for subversion. -* Incentives and mission are misaligned. -* Need directory & forum for Tendermint/SDK forks. - -## Partial Solution: adopt principles - -* Simplicity of design. -* The code is the spec. -* Minimal code - keep total footprint small. -* Minimal dependencies - all dependencies must get audited, and become part of - the repo. -* Modular dependencies - wherever reasonable, make components modular. -* Completeness - software projects that don't become finished are projects - that are forever vulnerable. One of the primary goals of the Gno language - and related works is to become finished within a reasonable timeframe. - -## What is already proposed for Tendermint2: - -* Complete Amino. -> multiplier of productivity for SDK development, to not - have to think about protobuf at all. Use "genproto" to even auto-generate - proto3 for encoding/decoding optimization through protoc. - - MISSION: be the basis for improving the encoding standard from proto3, because - proto3 length-prefixing is slow, and we need "proto4" or "amino2". - - LOOK at the [auto-generated proto files](https://github.com/gnolang/gno/blob/master/tm2/pkg/bft/consensus/consensus.proto)! - - There was work to remove this from the CosmosSDK because - Amino wasn't ready, but now that it is, it makes sense to incorporate it into - Tendermint2. - - -* Remove EvidenceReactor, Evidence, Violation: - - We need to make it easy to create alt mempool reactors. - - We "kill two birds with one stone" by implementing evidence as a first-class mempool lane. - - The authors of "ABCI++" have a different set of problems to solve, so we should do both! Tendermint++ - and Tendermint2. - - -* Fix address size to 20 bytes -> 160 is sufficient, and fixing it brings optimizations. - - -* General versionset system for handshake negotiation. -> So Tendermint2 can be - used as basis for other P2P applications. - - -* EventBus -> EventSwitch. -> For indexing, use an external system. - - To ensure Tendermint2 remains minimal and easily integrated with plugin modules, there is no internal implementation. - - The use of an EventSwitch makes the process simpler and synchronous, which maintains the determinism of Tendermint - tests. - - Keeping the Tendermint protocol synchronous is sufficient for optimal performance. - - However, if there is a need for asynchronous processing due to an exceptionally large number of validators, it should - be a separate fork with a unique name under the same taxonomy as Tendermint. - - -* Fix nondeterminism in consensus tests -> in relation to the above. - -* Add "MaxDataBytes" for total tx data size limitation. - - To avoid unexpected behavior caused by changes in validator size, it's best to allocate room for each module - separately instead of limiting the total block size as we did before. - -This way, we can ensure that there's enough space for all modules. - -* Remove external dependencies like prometheus - To ensure accuracy, all metrics and events should be integrated through interfaces. This may require extracting client - logic from Prometheus, but it will be incorporated into Tendermint2 and undergo the same auditing process as - everything else. - -* General consensus/WAL -> a WAL is useful enough to warrant being a re-usable - module. - -* Remove GRPC -> GRPC support should be plugged in (say in a GRPC fork of - Tendermint2), so alternative RPC protocols can likewise be. Tendermint2 aims - to be independent of the Protobuf stack so that it can retain freedom for - improving its codec. - -* Remove dependency on viper/cobra -> I have tried to strip out what we don't - use of viper/cobra for minimalism, but could not; and viper/cobra is one - prime target for malware to be introduced. Rather than audit viper/cobra, - Tendermint2 implements a cli convention for Go-structure-based flags and cli; - so if you still want to use viper/cobra you can do so by translating flags to - an options struct. - -* Question: Which projects use ABCI sockets besides CosmosSDK derived chains? - -## Roadmap - -First, we create a multi-organizational team for Tendermint2 & -TendermintCore/++ development. We will maintain a fork of the Tendermint++ repo -and suggest changes upstream based on our work on Tendermint2, while also -porting necessary fixes from Tendermint++ over to Tendermint2. - -We will also reach out to ecosystem partners and survey and create a -directory/taxonomy for Tendermint and CosmosSDK derivatives and manage a forum -for interfork collaboration. - -Ideally, Tendermint2 and TendermintCore/++ merge into one. - -## Challenge - -Either make a PR to Gaia/CosmosSDK/TendermintCore to be like Tendermint2, or -make a PR to Tendermint2 to import a feature or fix of TendermintCore. diff --git a/docs/concepts/testnets.md b/docs/concepts/testnets.md deleted file mode 100644 index b5286eaec57..00000000000 --- a/docs/concepts/testnets.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -id: testnets ---- - -# Gno Testnets - -This page documents all gno.land testnets, what their properties are, and how -they are meant to be used. For testnet configuration, visit the -[reference section](../reference/network-config.md). - -gno.land testnets are categorized by 4 main points: -- **Persistence of state** - - Is the state and transaction history persisted? -- **Timeliness of code** - - How up-to-date are Gno language features and demo packages & realms? -- **Intended purpose** - - When should this testnet be used? -- **Versioning strategy** - - How is this testnet versioned? - -Below you can find a breakdown of each existing testnet by these categories. - -## Portal Loop - -Portal Loop is an always up-to-date rolling testnet. It is meant to be used as -a nightly build of the Gno tech stack. The home page of [gno.land](https://gno.land) -is the `gnoweb` render of the Portal Loop testnet. - -- **Persistence of state:** - - State is kept on a best-effort basis - - Transactions that are affected by breaking changes will be discarded -- **Timeliness of code:** - - Packages & realms which are available in the `examples/` folder on the [Gno -monorepo](https://github.com/gnolang/gno) exist on the Portal Loop in matching -state - they are refreshed with every new commit to the `master` branch. -- **Intended purpose** - - Providing access the latest version of Gno for fast development & demoing -- **Versioning strategy**: - - Portal Loop infrastructure is managed within the -[`misc/loop`](https://github.com/gnolang/gno/tree/master/misc/loop) folder in the -monorepo - -For more information on the Portal Loop, and how it can be best utilized, -check out the [Portal Loop concept page](./portal-loop.md). Also, you can find -the Portal Loop faucet on [`gno.land/faucet`](https://gno.land/faucet). - -## Test5 - -Test5 a permanent multi-node testnet. It bumped the validator set from 7 to 17 -nodes, introduced GovDAO V2, and added lots of bug fixes and quality of life -improvements. - -Test5 was launched in November 2024. - -- **Persistence of state:** - - State is fully persisted unless there are breaking changes in a new release, - where persistence partly depends on implementing a migration strategy -- **Timeliness of code:** - - Pre-deployed packages and realms are at monorepo commit [2e9f5ce](https://github.com/gnolang/gno/tree/2e9f5ce8ecc90ee81eb3ae41c06bab30ab926150) -- **Intended purpose** - - Running a full node, testing validator coordination, deploying stable Gno - dApps, creating tools that require persisted state & transaction history -- **Versioning strategy**: - - Test5 is to be release-based, following releases of the Gno tech stack. - -## Test4 - -Test4 is the first permanent multi-node testnet, launched in July 2024. - -- **Persistence of state:** - - State is fully persisted unless there are breaking changes in a new release, - where persistence partly depends on implementing a migration strategy -- **Timeliness of code:** - - Versioning mechanisms for packages & realms will be implemented for test4 -- **Intended purpose** - - Running a full node, testing validator coordination, deploying stable Gno - dApps, creating tools that require persisted state & transaction history -- **Versioning strategy**: - - Test4 is the first gno.land testnet to be release-based, following releases -of the Gno tech stack. - -## Staging - -Staging is a testnet that is reset once every 60 minutes. - -- **Persistence of state:** - - State is fully discarded -- **Timeliness of code:** - - With every reset, the latest commit of the Gno tech stack is applied, including - the demo packages and realms -- **Intended purpose** - - Demoing, single-use code in a staging environment, testing automation which - uploads code to the chain, etc. -- **Versioning strategy**: - - Staging is reset every 60 minutes to match the latest monorepo commit - -## TestX - -These testnets are deprecated and currently serve as archives of previous progress. - -### Test3 (archive) - -The third Gno testnet. Archived data for test3 can be found [here](https://github.com/gnolang/tx-exports/tree/main/test3.gno.land). - -Launch date: November 4th 2022 -Release commit: [1ca2d97](https://github.com/gnolang/gno/commit/1ca2d973817b174b5b06eb9da011e1fcd2cca575) - -### Test2 (archive) - -The second Gno testnet. Find archive data [here](https://github.com/gnolang/tx-exports/tree/main/test2.gno.land). - -Launch date: July 10th 2022 -Release commit: [652dc7a](https://github.com/gnolang/gno/commit/652dc7a3a62ee0438093d598d123a8c357bf2499) - -### Test1 (archive) - -The first Gno testnet. Find archive data [here](https://github.com/gnolang/tx-exports/tree/main/test1.gno.land). - -Launch date: May 6th 2022 -Release commit: [797c7a1](https://github.com/gnolang/gno/commit/797c7a132d65534df373c63b837cf94b7831ac6e) diff --git a/docs/getting-started/getting-started.md b/docs/getting-started/getting-started.md deleted file mode 100644 index c60a0bfb433..00000000000 --- a/docs/getting-started/getting-started.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -id: getting-started ---- - -# Getting started - -Welcome to the **Getting Started** section for Gno. This section outlines how to -get started with Gno by using the Gno Playground, as well as how to set up a -local development environment, get funds, deploy packages, etc. diff --git a/docs/getting-started/local-setup/browsing-gnoland.md b/docs/getting-started/local-setup/browsing-gnoland.md deleted file mode 100644 index 8a555a3c07b..00000000000 --- a/docs/getting-started/local-setup/browsing-gnoland.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -id: browsing-gnoland ---- - -# Browsing gno.land - -## Overview -In this tutorial, you will learn how to browse [realms](../../concepts/realms.md) -and [packages](../../concepts/packages.md) deployed to a gno.land chain. -You will understand how the `Render` method is utilized to achieve realm state -visibility, and you will learn how to easily access realm APIs. - -## Prerequisites -- **`gnodev` installed.** Reference the -[Local Setup](installation.md#3-installing-other-gno-tools) guide for steps - -## 1. Start `gnodev` -To browse Gno source code, we need two components: -- a running `gno.land` node, -- a gno.land source code viewer, like `gnoweb` - -Luckily, `gnodev` comes prepackaged with both. To start, simply run `gnodev` in -your terminal. - -`gnodev` will start an in-memory node, as well as a `gnoweb` server: - -![gnodev](../../assets/getting-started/local-setup/browsing-gno-source-code/gnodev.gif) - -`gnodev` remembers the folder where it was installed, and loads the -[`examples/`](https://github.com/gnolang/gno/tree/master/examples) subfolder by default. By visiting -[`http://localhost:8888`](http://localhost:8888), you will be able to see the -gno.land home page: - -![homepage](../../assets/getting-started/local-setup/browsing-gno-source-code/gnoland-homepage.png) - -## 2. Browsing gno.land - -### Package source code -Packages in gno.land usually have names resembling `gno.land/p/`. Since -packages do not contain state, only their source code can be viewed on-chain. To -learn more about packages, check out the [Packages](../../concepts/packages.md) -concept page. - -Let's take a specific example: the `avl` package, deployed at `gno.land/p/demo/avl`. -To access the source code of the `avl` package, we can append the `/p/demo/avl` -to our browser URL (from the homepage). - -The final URL for the `avl` package source could be viewable at -[`http://127.0.0.1:8888/p/demo/avl`](http://127.0.0.1:8888/p/demo/avl), -if we followed default setup params, as we did in this guide. - -![gnoweb avl](../../assets/getting-started/local-setup/browsing-gno-source-code/gnoweb-avl.png) - -From here, we can open any source code file of the deployed on-chain package -and inspect its API. - -### Realm source code & state -In contrast to Packages, Realms in gno.land usually have names resembling -`gno.land/r/`. - -Realms _do_ contain state, and in addition to being able to view their source -code on-chain, users can also view their internal state representation in the -form of the `Render()` output. To learn more about realms, please check out the -[Realms](../../concepts/realms.md) concept page. - -We can browse the realm `Render()` method output and source code in our browser. -For example, the `gnoland/blog` realm is deployed at `gno.land/r/gnoland/blog`. - -To view the internal realm state of the `blog` realm, we can append the -`/r/gnoland/blog` to our browser URL (from the homepage). - -The final URL for the `blog` realm internal state could be viewable at -[`http://127.0.0.1:8888/r/gnoland/blog`](http://127.0.0.1:8888/r/gnoland/blog), -if we followed default setup params, as we did in this guide. - -![blog_render](../../assets/getting-started/local-setup/browsing-gno-source-code/blog_render.png) - -:::info Render() is not required -Internal realm state does not have to be exposed through the `Render()` method -of the realm, as it is not a requirement for deploying a Realm. -::: - -Additionally, to view the source code for the realm, we have two options: -- append `/` to the full realm path - [`http://127.0.0.1:8888/r/gnoland/blog/`](http://127.0.0.1:8888/r/gnoland/blog/) -- click on the `[source]` button in the top-right corner - -![blog_source](../../assets/getting-started/local-setup/browsing-gno-source-code/blog_source.png) - -Finally, the `[help]` button takes us to the realm help page, where you will -be able to see the user-facing API of the realm. - -![blog_help](../../assets/getting-started/local-setup/browsing-gno-source-code/blog_help.png) - -This page will allow you to easily generate `gnokey` commands for interacting -with the realm in question. - -## Conclusion - -That's it 🎉 - -You have successfully inspected the source code of a package and realm, and seen -the on-chain state of the blog app. You have also learned about the `[help]` page -that `gnoweb` provides. - diff --git a/docs/getting-started/local-setup/creating-a-keypair.md b/docs/getting-started/local-setup/creating-a-keypair.md deleted file mode 100644 index 983d732a0fd..00000000000 --- a/docs/getting-started/local-setup/creating-a-keypair.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -id: creating-a-keypair ---- - -# Creating a Keypair - -## Overview - -In this tutorial, you will learn how to create your Gno keypair using -[`gnokey`](../../gno-tooling/cli/gnokey/gnokey.md). - -Keypairs are the foundation of how users interact with blockchains; and Gno is -no exception. By using a 12-word or 24-word [mnemonic phrase](https://www.zimperium.com/glossary/mnemonic-seed/) -as a source of randomness, users can derive a private and a public key. -These two keys can then be used further; a public key derives an address which is -a unique identifier of a user on the blockchain, while a private key is used for -signing messages and transactions for the aforementioned address, proving a user -has ownership over it. - -Let's see how we can use `gnokey` to generate a Gno keypair locally. - -## Generating a keypair - -The `gnokey add` command allows you to generate a new keypair locally. Simply -run the command, while adding a name for your keypair: - -```bash -gnokey add MyKey -``` - -![gnokey-add-random](../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-random.gif) - -After running the command, `gnokey` will ask you to enter a password that will be -used to encrypt your keypair to the disk. Then, it will show you the following -information: -- Your public key, as well as the Gno address derived from it, starting with `g1...`, -- Your randomly generated 12-word mnemonic phrase which was used to derive the keypair. - -:::warning Safeguard your mnemonic phrase! - -A **mnemonic phrase** is like your master password; you can use it over and over -to derive the same keypairs. This is why it is crucial to store it in a safe, -offline place - writing the phrase on a piece of paper and hiding it is highly -recommended. **If it gets lost, it is unrecoverable.** - -::: - -`gnokey` will generate a keybase in which it will store information about your -keypairs. The keybase directory path is stored under the `-home` flag in `gnokey`. - -### Gno addresses - -Your **Gno address** is like your unique identifier on the network; an address -is visible in the caller stack of an application, it is included in each -transaction you create with your keypair, and anyone who knows your address can -send you [coins](../../concepts/stdlibs/coin.md), etc. - -## Conclusion - -That's it 🎉 - -You've successfully created your first Gno keypair. Check out -[Browsing gno.land](./browsing-gnoland.md) and -[Interacting with gno.land](./interacting-with-gnoland.md) to see how you can -use it. - -If you wish to learn more about `gnokey` specifically, check out the -[gnokey section](../../gno-tooling/cli/gnokey/gnokey.md). - - - - - - - - - diff --git a/docs/getting-started/local-setup/installation.md b/docs/getting-started/local-setup/installation.md deleted file mode 100644 index e05c2f9b205..00000000000 --- a/docs/getting-started/local-setup/installation.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -id: installation ---- - -# Installation - -## Overview -In this tutorial, you will learn how to set up the Gno development environment -locally, so you can get up and running writing Gno code. You will download and -install all the necessary tooling, and validate that it is correctly configured -to run on your machine. - -## Prerequisites -- **Git** -- **`make` (for running Makefiles)** -- **Go 1.22+** -- **Go Environment Setup**: - - Make sure `$GOPATH` is well-defined, and `$GOPATH/bin` is added to your `$PATH` variable. - - To do this, you can add the following line to your `.bashrc`, `.zshrc` or other config file: -``` -export GOPATH=$HOME/go -export PATH=$GOPATH/bin:$PATH -``` - -## 1. Cloning the repository -To get started with a local gno.land development environment, you must clone the -GitHub repository somewhere on disk: - -```bash -git clone https://github.com/gnolang/gno.git -``` - -## 2. Installing the required tools - -There are three tools that should be used for getting started with Gno development: -- `gno` - the GnoVM binary -- `gnodev` - the Gno [development helper](../../gno-tooling/cli/gnodev.md) -- `gnokey` - the Gno [keypair manager](../../gno-tooling/cli/gnokey/working-with-key-pairs.md) - -To install all three tools, simply run the following in the root of the repo: -```bash -make install -``` - -## 3. Verifying installation - -### `gno` -`gno` provides ample functionality to the user, among which is running, -transpiling, testing and building `.gno` files. Read more -about `gnokey` [here](../../gno-tooling/cli/gno.md). - -To verify the `gno` binary is installed system-wide, you can run: - -```bash -gno --help -``` - -You should get the help output from the command: - -![gno help](../../assets/getting-started/local-setup/local-setup/gno-help.gif) - -Alternatively, if you don't want to have the binary callable system-wide, you -can run the binary directly: - -```bash -cd gnovm -go run ./cmd/gno --help -``` - -### `gnodev` -`gnodev` is the go-to Gno development helper tool - it comes with a built in -gno.land node, a `gnoweb` server to display the state of your smart contracts -(realms), and a watcher system to actively track changes in your code. Read more -about `gnodev` [here](../../gno-tooling/cli/gnodev.md). - -To verify that the `gnodev` binary is installed system-wide, you can run: - -```bash -gnodev -``` - -You should get the following output: -![gnodev](../../assets/getting-started/local-setup/local-setup/gnodev.gif) - - -### `gnokey` - -`gnokey` is the gno.land keypair management CLI tool. It allows you to create -keypairs, sign transactions, and broadcast them to gno.land chains. Read more -about `gnokey` [here](../../gno-tooling/cli/gnokey/gnokey.md). - -To verify that the `gnokey` binary is installed system-wide, you can run: - -```bash -gnokey --help -``` - -You should get the help output from the command: - -![gnokey help](../../assets/getting-started/local-setup/local-setup/gnokey-help.gif) - -## Conclusion - -That's it 🎉 - -You have successfully built out and installed the necessary tools for Gno -development! - -In further documents, you will gain a better understanding of how they are used -to make Gno work. diff --git a/docs/getting-started/local-setup/interacting-with-gnoland.md b/docs/getting-started/local-setup/interacting-with-gnoland.md deleted file mode 100644 index 6b4b8213228..00000000000 --- a/docs/getting-started/local-setup/interacting-with-gnoland.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -id: interacting-with-gnoland ---- - -# Interacting with gno.land code - -## Overview -In this tutorial, you will learn how to interact with gno.land code. -You will understand how to use your keypair to send transactions to realms -and packages, send native coins, and more. - -## Prerequisites - -- **`gnokey` installed.** Reference the -[Local Setup](installation.md) guide for steps -- **A keypair in `gnokey`.** Reference the [Creating a key pair](creating-a-keypair.md) guide for steps - -## 1. Get testnet GNOTs -For interacting with any gno.land chain, you will need a certain amount of GNOTs -to pay gas fees with. - -For this example, we will use the [Portal Loop](../../concepts/testnets.md#portal-loop) -testnet. We can access the Portal Loop faucet through the -[Gno Faucet Hub](https://faucet.gno.land). - -![faucet-hub](../../assets/getting-started/local-setup/interacting-with-gnoland/faucet-hub.png) - -After choosing "Gno Portal Loop", you will get a prompt to input your address, -select the amount of testnet GNOT you want to receive, and solve a captcha: - -![faucet-hub-portal-loop](../../assets/getting-started/local-setup/interacting-with-gnoland/faucet-hub-portal-loop.png) - -After inputting your address and solving the captcha, you can check if you have received funds with the -following `gnokey` command: - -```bash -gnokey query bank/balances/ --remote "https://rpc.gno.land:443" -``` - -If the faucet request was successful, you should see something similar to the -following: - -``` -❯ gnokey query bank/balances/ --remote "https://rpc.gno.land:443" -height: 0 -data: "10000000ugnot" -``` - -## 2. Visit a realm - -For this example, we will use the [Userbook realm](https://gno.land/r/demo/userbook). -The Userbook realm is a simple app that allows users to sign up, and keeps track -of when they signed up. It also displays the currently signed-up users and the block -height at which they have signed up. - -![userbook-default](../../assets/getting-started/local-setup/interacting-with-gnoland/userbook-default.png) - -> Note: block heights in this case are unreliable because of the way the Portal Loop -> network works. -> Read more [here](../../concepts/portal-loop.md). - -To see what functions are available to call on the Userbook realm, click -the `[help]` button. - -![userbook-help](../../assets/getting-started/local-setup/interacting-with-gnoland/userbook-help.png) - -By choosing one of the two `gnokey` commands and inputting your address -(or keypair name) in the top bar, you will have a ready command to paste into your -terminal. For example, the following command will call the `SignUp` function with the -keypair `MyKey`: - -``` -gnokey maketx call \ --pkgpath "gno.land/r/demo/userbook" \ --func "SignUp" \ --gas-fee 1000000ugnot \ --gas-wanted 2000000 \ --send "" \ --broadcast \ --chainid "portal-loop" \ --remote "https://rpc.gno.land:443" \ -MyKey -``` - -To see what each option and flag in this command does, check out `gnokey` in the -[tooling section](../../gno-tooling/cli/gnokey/gnokey.md). - -## Conclusion - -That's it! Congratulations on executing your first transaction on a Gno network! 🎉 - -If the previous transaction was successful, you should be able -to see your address on the main page of the Userbook realm. - -This concludes the "Local Setup" section. For next steps, see the -[How-to guides section](../../how-to-guides/how-to-guides.md), where you will -learn how to write your first realm, package, and much more. diff --git a/docs/getting-started/local-setup/local-setup.md b/docs/getting-started/local-setup/local-setup.md deleted file mode 100644 index f0136f45638..00000000000 --- a/docs/getting-started/local-setup/local-setup.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -id: local-setup ---- - -# Local setup - -This section will show you how to set up a local environment for Gno development. -It includes instructions for installation, setting up a gno.land keypair, -browsing Gno source code, and more. diff --git a/docs/getting-started/playground-start.md b/docs/getting-started/playground-start.md deleted file mode 100644 index 0da950b69c0..00000000000 --- a/docs/getting-started/playground-start.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -id: playground-start ---- - -# Gno Playground - -## Overview - -The Gno Playground is an innovative web-based editor and sandbox that enables developers to -interactively work with the Gno language. It makes coding, testing, -and deploying simple with its diverse set of tools and features. Users can -share code, run tests, and deploy projects to gno.land networks, -making it the perfect tool to get started with Gno development. - -## Prerequisites - -- **A gno.land compatible wallet** - - Currently, [Adena](https://www.adena.app/) is the preferred wallet for -gno.land, with more wallets being introduced in the future. - -## Playground Features - -To get started, visit the Playground at [play.gno.land](https://play.gno.land). You will be greeted with a -simple `package.gno` file: - -![default_playground](../assets/getting-started/playground/default_playground.png) - -The Playground has the following features: -- `Share` - Generate a unique, short, and shareable identifier for your Gno code. -- `Deploy` - Connect your wallet and publish your code on gno.land. -- `Format` - Automatically adjust your Gno code's structure and style for optimal readability and consistency. -- `Run` - Execute a particular expression within your code to validate its functionality and output. -- `Test` - Execute predefined tests to verify your code's integrity and ensure it meets expected outcomes. -- `REPL` - Experiment and troubleshoot in real-time using the GnoVM with interactive REPL features. - -Let's dive into each of the Playground features. - -### Share - -The **Share** feature provides users with a permanent shortlink to the current -Gno code in the playground, making it a simple and easy way to do code-sharing. -Links created via the **Share** feature initially set to expire after 5 years, -ensuring the shared code remains accessible over an extended period. - -### Deploy - -The **Deploy** feature allows users to seamlessly deploy their Gno code to the -chain. After connecting a gno.land wallet, users can select their desired -package path and network for deployment. - -![default_deploy](../assets/getting-started/playground/default_deploy.png) - -After inputting your desired package path, you can select the network you would -like to deploy to, such as [Portal Loop](../concepts/portal-loop.md) or local, -and click deploy. - -:::info -The Playground will automatically provide enough test tokens to cover the gas -cost at the time of deployment, removing the need for using a faucet. -::: - -### Format - -The **Format** feature utilizes the Monaco editor and -[`gofmt`](https://pkg.go.dev/cmd/gofmt) to automatically refine and standardize -your Gno code's syntax. - -### Run - -The **Run** feature will allow you to run an expression on your Gno code. Take the following code -for an example: - -[![run_example](../assets/getting-started/playground/run.png)](https://play.gno.land/p/nBq2W8drjMy) - -Running `println(Render("Gnopher"))` will display the following output: - -```bash -Hello Gnopher! -``` - -View the code [here](https://play.gno.land/p/nBq2W8drjMy). - -### Test - -The **Test** feature will look for `_test.gno` files in your playground and run -the`gno test -v` command on them. Testing your code will open a terminal that -will show you the output of the test. Read more about how Gno tests work -[here](../concepts/gno-test.md). - -### REPL (experimental) - -The **REPL** feature allows you to experiment with the GnoVM. -It provides a command-line interface for hands-on learning, iterative testing, and swift prototyping. - -## Learning about gno.land & writing Gno code - -If you're new here, don't worry—content is regularly produced to breakdown -gno.land to explain its features. Dive into the essentials of gno.land by -exploring the [Concepts](../concepts/concepts.md) section. - -To get started writing Gno code, check out the -[How-to](../how-to-guides/how-to-guides.md) section, the `examples/` folder on -the [Gno monorepo](https://github.com/gnolang/gno), or one of many community projects and tutorials found in the -[awesome-gno](https://github.com/gnolang/awesome-gno/blob/main/README.md) repo on GitHub. diff --git a/docs/gno-infrastructure/gno-infrastructure.md b/docs/gno-infrastructure/gno-infrastructure.md deleted file mode 100644 index aa301fa79d4..00000000000 --- a/docs/gno-infrastructure/gno-infrastructure.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -id: gno-infrastructure ---- - -# Gno Infrastructure - -Welcome to the **Gno Infrastructure** section. This section is meant for users -wanting to learn how to run their own Gno node, set up their own faucet, run -an indexer service, and more. diff --git a/docs/gno-infrastructure/premining-balances.md b/docs/gno-infrastructure/premining-balances.md deleted file mode 100644 index 0755674543e..00000000000 --- a/docs/gno-infrastructure/premining-balances.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -id: premining-balances ---- - -# Premining Balances - -## Overview - -In this tutorial, you will gain an understanding on how to premine native currency on a local gno.land chain. -Additionally, you will understand how to query the account balance after you premine it. - -Premining balance is the process of making sure some accounts (addresses) have specific funds when the chain initially -launches. In the context of local chain deployments, premine balances are used to ensure the user accounts (developers) -have ample funds to interact with the chain and facilitate contract deployments. - -## Prerequisites - -- **`gnoland` set up. Reference - the [Setting up a local chain](validators/validators-setting-up-a-new-chain#installation) - guide for steps** -- **`gnokey` set up. Reference - the [Installation](../getting-started/local-setup/installation.md#2-installing-the-required-tools-) guide - for steps** - -## 1. Clean chain data - -In order for us to premine funds on a fresh chain, we need to make sure we do not have any leftover blockchain data -from previous chain runs. - -The blockchain node, when it runs, works with an embedded DB locally on disk to store execution data (such as -configuration files, or the state DB). For Gno blockchain nodes, this working directory is labeled as `gnoland-data` by -default. - -To clean out old blockchain data, navigate to the `gno.land` folder and run the appropriate make command: - -```bash -cd gno.land -make fclean -``` - -## 2. Change the `genesis_balances.txt` file - -When the Gno node boots up, among other things, it reads a file called `genesis_balances.txt` to generate the initial -balance set for the blockchain. - -An example of how this looks like in the initial `genesis.json` file after the chain starts: - -```bash - "app_state": { - "@type": "/gno.GenesisState", - "balances": [ - "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5=10000000000000ugnot", - "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj=10000000000000ugnot", - "g1f4v282mwyhu29afke4vq5r2xzcm6z3ftnugcnv=1000000000000ugnot", - "g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa=1000000000000ugnot" - ], -``` - -The `genesis_balances.txt` file is located at `./gno.land/genesis/genesis_balances.txt`. -To add a new entry to the premine table, simply append a line to the end of the file: - -```bash -g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt=10000000000ugnot # My address -``` - -Replace `g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt` with the address you want balance on, and `10000000000ugnot` with the -desired `ugnot` balance. - -## 3. Start the local chain - -Now that our address and the desired premine balance are located in the `genesis_balances.txt` file, we can start the -local Gno node. -To run the local Gno node, make sure you are in the `gno.land` sub-folder, and run the appropriate make command: - -```bash -cd gno.land -gnoland start -``` - -This command will initialize the Gno node, generate the `genesis.json` with our newly added premine information, and -start the chain. -![gnoland start](../assets/getting-started/local-setup/setting-up-funds/gnoland-start.gif) - -## 3. Check the account balance - -To check the balance of any account (or the account we just premined), we can use the following ABCI query: - -```bash -gnokey query --remote localhost:26657 bank/balances/g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt -``` - -Let's break down this command: - -- **`--remote`** - the JSON-RPC URL of the running Gno node. In the case of a local deployment, the default value - is `localhost:26657` -- **`bank/balances/g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt`** - the ABCI query targets the `bank` module to find - the `balances` for address `g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt`. Replace the address with your desired address - ![gnokey query](../assets/getting-started/local-setup/setting-up-funds/gnokey-query.gif) - -## Conclusion - -That's it 🎉 -You have successfully premined a native currency balance on a locally-running Gno chain! -Additionally, you have also learned how to query the native currency balance for an address, using built-in ABCI queries -and the `gnokey` tool. diff --git a/docs/gno-infrastructure/validators/connect-to-existing-chain.md b/docs/gno-infrastructure/validators/connect-to-existing-chain.md deleted file mode 100644 index f15f6bb59e2..00000000000 --- a/docs/gno-infrastructure/validators/connect-to-existing-chain.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -id: validators-connect-to-and-existing-gno-chain ---- - -# Connect to an Existing Gno Chain - -## Overview - -In this tutorial, you will learn how to start a local Gno node and connect to an existing Gno chain (like a testnet). - -## Prerequisites - -- **Git** -- **`make` (for running Makefiles)** -- **Go 1.22+** -- **Go Environment Setup**: Ensure you have Go set up as outlined in - the [Go official installation documentation](https://go.dev/doc/install) for your environment - -## 1. Initialize the node directory - -To initialize a new gno.land node working directory (configuration and secrets), make sure to -follow [Step 1](./setting-up-a-new-chain.md#1-generate-the-node-directory-secrets--config) from the -chain setup tutorial. - -## 2. Obtain the `genesis.json` of the remote chain - -The genesis file of target chain is required in order to initialize the local node. - -:::info - -The genesis file will -be [easily downloadable from GitHub](https://github.com/gnolang/gno/issues/1836#issuecomment-2049428623) in the future. - -For now, obtain the file by - -1. Sharing via scp or ftp -2. Fetching it from `{chain_rpc:26657}/genesis` (might result in time-out error due to large file sizes) - -::: - -## 3. Confirm the validator information of the first node. - -```bash -gnoland secrets get node_id - -{ - "id": "g17h5t86vrztm6vuesx0xsyrg90wplj9mt9nsxng", - "p2p_address": "g17h5t86vrztm6vuesx0xsyrg90wplj9mt9nsxng@0.0.0.0:26656" -} -``` - -### Public IP of the Node - -You need the IP information about the network interface that you wish to connect from external nodes. - -If you wish to only connect from nodes in the same network, using a private IP should suffice. - -However, if you wish to connect from all nodes without any specific limitations, use your public IP. - -```bash -curl ifconfig.me/ip # GET PUBLIC IP - -# 1.2.3.4 # USE YOUR OWN PUBLIC IP -``` - -## 4. Configure the `persistent_peers` list - -We need to configure a list of nodes that your validators will always retain a connection with. -To get the local P2P address of the current node (these values should be obtained from remote peers): - -```bash -gnoland secrets get node_id.p2p_address - -"g17h5t86vrztm6vuesx0xsyrg90wplj9mt9nsxng@0.0.0.0:26656" -``` - -We can use this P2P address value to configure the `persistent_peers` configuration value - -```bash -gnoland config set p2p.persistent_peers "g19d8x6tcr2eyup9e2zwp9ydprm98l76gp66tmd6@1.2.3.4:26656" -``` - -## 5. Configure the seeds - -We should configure the list of seed nodes. Seed nodes provide information about other nodes for the validator to -connect with the chain, enabling a fast and stable initial connection. These seed nodes are also called _bootnodes_. - -:::warning - -The option to activate the Seed Mode from the node is currently missing. - -::: - -```bash -gnoland config set p2p.seeds "g19d8x6tcr2eyup9e2zwp9ydprm98l76gp66tmd6@1.2.3.4:26656" -``` - -## 6. Start the node - -Now that we've set up the local node configuration, and added peering info, we can start the gno.land node: - -```shell -gnoland start \ ---genesis ./genesis.json \ ---data-dir ./gnoland-data -``` - -That's it! 🎉 - -Your new Gno node should be up and running, and syncing block data from the remote chain. diff --git a/docs/gno-infrastructure/validators/faq.md b/docs/gno-infrastructure/validators/faq.md deleted file mode 100644 index 940d3abe7a1..00000000000 --- a/docs/gno-infrastructure/validators/faq.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -id: validators-faq ---- - -# Validators FAQ - -## General Concepts - -### What is a gno.land validator? - -gno.land is based on [Tendermint2](https://docs.gno.land/concepts/tendermint2) that relies on a set of validators -selected based on [Proof of Contribution](https://docs.gno.land/concepts/proof-of-contribution) (PoC) to secure the -network. Validators are tasked with participating in consensus by committing new blocks and broadcasting votes. -Validators are compensated with a portion of transaction fees generated in the network. In gno.land, the voting power of -all validators are equally weighted to achieve a high nakamoto coefficient and fairness. - -### What is Tendermint2? - -[Tendermint2](https://docs.gno.land/concepts/tendermint2) (TM2) is the consensus protocol that powers gno.land. TM2 is a -successor of [Tendermint Core](https://github.com/tendermint/tendermint2), a de facto consensus framework for building -Proof of Stake blockchains. The design philosophy of TM2 is to create “complete software” without any vulnerabilities -with development focused on minimalism, dependency removal, and modularity. - -### What is Proof of Contribution? - -[Proof of Contribution](https://docs.gno.land/concepts/proof-of-contribution) (PoC) is a novel consensus mechanism that -secures gno.land. PoC weighs expertise and alignment with the project to evaluate the contribution of individuals or -teams who govern and operate the chain. Unlike Proof of Stake (PoS), validators are selected via governance of -Contributors based on their reputation and technical proficiency. The voting power of the network is equally distributed -across all validators for higher decentralization. A portion of all transaction fees paid to the network are evenly -shared between all validators to provide a fair incentive structure. - -### How does gno.land differ from the Cosmos Hub? - -In Cosmos Hub, validators are selected based on the amount of staked `ATOM` tokens delegated. This means that anyone -with enough capital can join as a validator only to seek economic incentives without any alignment or technical -expertise. This system leads to an undesirable incentive structure in which validators are rewarded purely based on the -capital delegated, regardless of the quality of their infrastructure or service. - -On the contrary, validators in gno.land must be reviewed and verified to have made significant contributions in order to -join the validator set. This property resembles the validator selection mechanism -in [Proof of Authority](https://openethereum.github.io/Proof-of-Authority-Chains). Furthermore, all validators are -evenly rewarded to ensure that the entire validator set is fairly incentivized to ensure the sustainability of the -network. - -### What stage is the gno.land project in? - -gno.land is currently in Testnet 3, the single-node testnet stage. The next version, Testnet 4, is scheduled to go live -in Q3 2024, which will include a validator set implementation for a multinode environment. - -## Becoming a Validator - -### How do I join the testnet as a validator? - -Out of many official Gno testnets, Testnet4 (`test4`) is the purpose-built network for testing the multi-node validator -environment prior to mainnet launch. Testnet4 is scheduled to go live in Q3 2024 with genesis validators consisting of -the Gno Core Team, partners, and external contributors. - -For more information about joining testnet4, -visit [the relevant issue](https://github.com/gnolang/hackerspace/issues/69). For more information about different -testnets, visit [Gno Testnets](https://docs.gno.land/concepts/testnets). - -### What are the incentives for running a validator? - -Network transaction fees paid on the gno.land in `GNOT` are collected, from which a portion is directed to reward -validators for their work. All validators fairly receive an equal amount of rewards. - -### How many validators will there be in mainnet? - -The exact plans for mainnet are still TBD. Based on the latest discussions between contributors, the mainnet will likely -have an inital validator set size of 20~50, which will gradually scale with the development and decentralization of the -gno.land project. - -### How do I make my first contribution? - -gno.land is in active development and external contributions are always welcome! If you’re looking for tasks to begin -with, we suggest you visit -the [Bounties &](https://github.com/orgs/gnolang/projects/35/views/3) [Worx](https://github.com/orgs/gnolang/projects/35/views/3) -board and search for open tasks up for grabs. Start from small challenges and work your way up to the bigger ones. Every -contribution is acknowledged and highly regarded in PoC. We look forward to having you onboard as a new Contributor! - -## Technical Guides - -### What are the different types of keys? - -1. **Tendermint ( Tendermint2 ) Key :** A unique key used for voting in consensus during creation of blocks. A - Tendermint Key is also often called a Validator Key. It is automatically created when running - the `gnoland secrets init` command. A validator may check their Tendermint Key by running - the `gnoland secrets get validator_key` command. - -2. **User-owned keys :** A key that is generated when a new account is created using the `gnokey` command. It is used to - sign transactions. - -3. **Node Key :** A key used for communicating with other nodes. It is automatically created when running - the `gnoland secrets init` command. A validator may check their Node Key by running the `gnoland secrets get node_id` - command. - -### What is a full node and a pruned node? - -A full node fully validates transactions and blocks of a blockchain and keeps a full record of all historic activity. A -pruned node is a lighter node that processes only block headers and does not keep all historical data of the blockchain -post-verification. Pruned nodes are less resource intensive in terms of storage costs. Although validators may run -either a full node or a pruned node, it is important to retain enough blocks to be able to validate new blocks. - -## Technical References - -### How do I initialize `gno secrets`? - -The `gno secrets init` command allows you to initialize the private information required to run the validator, including -the validator node's private key, the state, and the node ID. Refer -to [this section](../../gno-tooling/cli/gnoland.md#gnoland-secrets-init-flags) for various flags that allow you to -define the output directory or to overwrite the existing secrets. - -### How do I get `gno secrets`? - -To retrieve the private information of your validator node, use the `gnoland-secrets-get` command. Refer -to [this section](../../gno-tooling/cli/gnoland.md#gnoland-secrets-get-flags) for a flag that allows you to define the -output directory. - -### How do I initialize the gno node configurations? - -To initialize the configurations required to run a node, use the `gnoland config init` command. Refer -to [this section](../../gno-tooling/cli/gnoland.md#gnoland-config-init-flags) for various flags that allow you to define -the path or to overwrite the existing configurations. - -### How do I get the current gno node configurations? - -To retrieve the specific values the current gno node configurations, use the `gnoland config get` command. Refer -to [this section](../../gno-tooling/cli/gnoland.md#gnoland-config-get) for a flag that allows you to define the path to -the configurations file. - -### How do I edit the gno node configurations? - -To edit the specific value of gno node configurations, use the `gnoland-config set` command. Refer -to [this section](../../gno-tooling/cli/gnoland.md#gnoland-config-set) for a flag that allows you to define the path to -the configurations file. - -### How do I initialize and start a new gno chain? - -To start an independent gno chain, follow the initialization process available -in [this section](./setting-up-a-new-chain.md). - -### How do I connect to an existing gno chain? - -To join the validator set of a gno chain, you must first establish a connection. Refer -to [this section](./connect-to-existing-chain.md) for a step-by-step guide on how to connect to an existing gno -chain. diff --git a/docs/gno-infrastructure/validators/overview.md b/docs/gno-infrastructure/validators/overview.md deleted file mode 100644 index e0973ad22d1..00000000000 --- a/docs/gno-infrastructure/validators/overview.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -id: validators-overview ---- - -# Validator Overview - -## Introduction - -gno.land is a blockchain powered by the Gno tech stack, which consists of -the [Gno Language](https://docs.gno.land/concepts/gno-language/) (Gno), -[Tendermint2](https://docs.gno.land/concepts/tendermint2/) (TM2), -and [GnoVM](https://docs.gno.land/concepts/gnovm/). Unlike -existing [Proof of Stake](https://docs.cosmos.network/v0.46/modules/staking/) (PoS) blockchains in the Cosmos ecosystem, -gno.land runs on [Proof of Contribution](https://docs.gno.land/concepts/proof-of-contribution/) (PoC), a novel -reputation-based consensus mechanism that values expertise and alignment with the project. In PoC, validators are -selected via governance based on their contribution to the project and technical proficiency. The voting power of the -network is equally distributed across all validators to achieve a high nakamoto coefficient. A portion of all -transaction fees paid to the network are evenly shared between all validators to provide a fair incentive structure. - -| **Blockchain** | Cosmos | gno.land | -|--------------------------------------|-------------------------|-------------------------------| -| **Consensus Protocol** | Comet BFT | Tendermint2 | -| **Consensus Mechanism** | Proof of Stake | Proof of Contribution | -| **Requirement** | Delegation of Stake | Contribution | -| **Voting Power Reward Distribution** | Capital-based | Evenly-distributed | -| **Number of Validators** | 180 | 20~200 (gradually increasing) | -| **Virtual Machine** | N/A | GnoVM | -| **Tokenomics** | Inflationary (Dilutive) | Deflationary (Non-dilutive) | - -## Hardware Requirements - -The following minimum hardware requirements are recommended for running a validator node. - -- Memory: 16 GB RAM (Recommended: 32 GB) -- CPU: 2 cores (Recommended: 4 cores) -- Disk: 100 GB SSD (Depends on the level of pruning) - -:::warning - -These hardware requirements are currently approximate based on the Cosmos validator specifications. Final requirements -will be determined following thorough testing and optimization experiments in Testnet 4. - -::: - -## Good Validators - -Validators for gno.land are trusted to demonstrate professionalism and responsibility. Below are best practices that can -be expected from a good, reliable validator. - -#### Ecosystem Contribution - -- Contributing to the core development of the gno.land project -- Providing useful tools or infrastructure services (wallets, explorers, public RPCs, etc.) -- Creating educational materials to guide new members -- Localizing documentation or content to lower language or cultural barriers - -#### Quality Infrastructure - -- Strong connectivity, CPU, and memory setup -- Exercising technical stability by retaining a high uptime with a robust monitoring system -- Robust contingency plans with failover systems, storage backups, and redundant power supplies -- Geographical distribution of servers - -#### Transparency - -- Providing regular updates -- Engaging actively in community discussions -- Being accountable for any failures - -#### Compliance - -- Exercising legal compliance -- Consulting with legal experts to identify regulatory risks -- Conducting internal audits - -## Community - -Join the official gno.land community in various channels to receive the latest updates about the project and actively -communicate with other validators and contributors. - -- [gno.land Blog](https://gno.land/r/gnoland/blog) -- [gno.land Discord](https://discord.gg/YFtMjWwUN7) -- [gno.land Twitter](https://x.com/_gnoland) - -:::info - -The validator set implementation in gno.land is abstracted away from the consensus mechanism inside the `r/sys/vals` -realm. The realm is not production ready yet, and is still under active development. Proposals and contributions to -improve and complete the implementation are welcome. - -**Links to related efforts:** - -- Validator set injection through a Realm [[gnolang/gno #1823]](https://github.com/gnolang/gno/issues/1823) -- Add Validator Set Realm / Package [[gnolang/gno #1824]](https://github.com/gnolang/gno/issues/1824) -- Add `/r/sys/vals` [[gnolang/gno #2130]](https://github.com/gnolang/gno/pull/2130) -- Add valset injection through `r/sys/vals` [[gnolang/gno #2229]](https://github.com/gnolang/gno/pull/2229) - -::: diff --git a/docs/gno-infrastructure/validators/running-a-validator.md b/docs/gno-infrastructure/validators/running-a-validator.md deleted file mode 100644 index d13fa5d216e..00000000000 --- a/docs/gno-infrastructure/validators/running-a-validator.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -id: validators-running-a-validator ---- - -# Running a Validator - -## Becoming a gno.land validator - -The gno.land blockchain is powered by the [Tendermint2](https://docs.gno.land/concepts/tendermint2) (TM2) consensus, -which involves committing of new blocks and broadcasting votes by multiple validators selected via governance -in [Proof of Contribution](https://docs.gno.land/concepts/proof-of-contribution) (PoC). While traditional Proof of -Stake (PoS) blockchains such as the Cosmos Hub required validators to secure a delegation of staked tokens to join the -validator set, no bonding of capital is involved in gno.land. Rather, the validators on gno.land are expected to -demonstrate their technical expertise and alignment with the project by making continuous, meaningful contributions to -the project. Furthermore, the voting power and the transaction fee rewards between validators are distributed evenly to -achieve higher decentralization. From a technical perspective, the validator set implementation in gno.land as its -abstracted away into the `r/sys/vals` realm ([work in progress](https://github.com/gnolang/gno/issues/1824)), as a form -of smart-contract, for modularity, whereas existing blockchains include the validator management logic within the -consensus layer. - -# Start a New Gno Chain and a Validator - -- [start a new gno chain and a validator](./setting-up-a-new-chain.md) - -# Connect to an Existing Gno Chain - -- [connect to an existing gno chain](./connect-to-existing-chain.md) diff --git a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md deleted file mode 100644 index 5db8a7f1a59..00000000000 --- a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md +++ /dev/null @@ -1,455 +0,0 @@ ---- -id: validators-setting-up-a-new-chain ---- - -# Setting up a Local Chain - -## Overview - -In this tutorial, you will learn how to start a local Gno node (and chain!). -Additionally, you will see the different options you can use to make your Gno instance unique. - -## Prerequisites - -- **Git** -- **`make` (for running Makefiles)** -- **Go 1.22+** -- **Go Environment Setup**: Ensure you have Go set up as outlined in - the [Go official installation documentation](https://go.dev/doc/install) for your environment - -## Installation - -To install the `gnoland` and `gnogenesis` binaries, clone the Gno monorepo: - -```bash -git clone https://github.com/gnolang/gno.git -``` - -After cloning the repo, go into the `gno.land/` folder, and use the existing -Makefile to install the `gnoland` binary: - -```bash -cd gno.land -make install.gnoland && make -C contribs/gnogenesis install -``` - -To verify that you've installed the binary properly and that you are able to use -it, run the `gnoland` command: - -```bash -gnoland --help -``` - -If you do not wish to install the binary globally, you can build and run it -with the following command from the `gno.land/` folder: - -```bash -make build.gnoland -``` - -And finally, run it with `./build gnoland`. - -## Starting a local node (lazy init) - -You can start a Gno blockchain node with the default configuration by navigating to the `gno.land` sub-folder and -running the following command: - -```bash -gnoland start --lazy -``` - -The command will trigger a chain initialization process (if you haven't run the node before), and start the Gno node, -which is ready to accept transactions and interact with other Gno nodes. - -![gnoland start](../../assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif) - -:::info Lazy init - -Starting a Gno blockchain node using just the `gnoland start --lazy` command implies a few things: - -- the default configuration will be used, and generated on disk in the `gnoland-data` directory -- random secrets data will be generated (node private keys, networking keys...) -- an entirely new `genesis.json` will be used, and generated on disk in the `../gnoland-data` directory. The genesis - will have a single validator, whose public key is derived from the previously generated node secrets - -::: - -To view the command defaults, simply run the `help` command: - -```bash -gnoland start --help -``` - -Let's break down the most important default settings: - -- `chainid` - the ID of the Gno chain. This is used for Gno clients, and distinguishing the chain from other Gno - chains (ex. through IBC) -- `genesis-balances-file` - the initial premine balances file, which contains initial native currency allocations for - the chain. By default, the genesis balances file is located in `gno.land/genesis/genesis_balances.txt`, this is also - the - reason why we need to navigate to the `gno.land` sub-folder to run the command with default settings -- `data-dir` - the working directory for the node configuration and node data (state DB) - -:::info Resetting the chain - -As mentioned, the working directory for the node is located in `data-dir`. To reset the chain, you need -to delete this directory and `genesis.json`, then start the node up again. If you are using the default node -configuration, you can run -`make fclean` from the `gno.land` sub-folder to delete the `gnoland-data` working directory. - -::: - -## Starting a local node (manual configuration) - -Manually configuring and starting the Gno blockchain node is a bit more involved than simply initializing it "lazily", -and involves the following steps: - -- generating the node secrets, and configuration -- generating the `genesis.json`, and populating it -- starting the node with the generated data - -### 1. Generate the node directory (secrets + config) - -You can generate the default node directory secrets using the following command: - -```shell -gnoland secrets init -``` - -And generate the default node config using the following command: - -```shell -gnoland config init -``` - -This will initialize the following directory structure: - -```shell -. -└── gnoland-data/ - ├── secrets/ - │ ├── priv_validator_state.json - │ ├── node_key.json - │ └── priv_validator_key.json - └── config/ - └── config.toml -``` - -A couple of things to note: - -- `gnoland config init` initializes a default configuration -- `gnoland secrets init` initializes new node secrets (validator key, node p2p key) - -Essentially, `gnoland start --lazy` is simply a combination of `gnoland secrets init` and `gnoland config init`, -with the default options enabled. - -#### Changing the node configuration - -To change the configuration params, such as for example the node's listen address, you can utilize the following -command: - -```shell -gnoland config set rpc.laddr tcp://0.0.0.0:26657 -``` - -This will update the RPC listen address to `0.0.0.0:26657`. You can verify the configuration was updated by running: - -```bash -gnoland config get rpc.laddr - -# similar behavior for cosmos validator -# gaiad tx staking create-validator `--node string (default:tcp://localhost:26657)` -``` - -:::tip - -A moniker is a human-readable name of your Gno node. You may customize your moniker with the following -command: - -```bash -gnoland config set moniker node01 -``` - -::: - -:::warning Modify existing secrets - -We can modify existing secrets, or utilize our own (if we have them backed up, for example) for the gno.land node. -Each secret needs to be placed in the appropriate path within `/secrets`, and it can be replaced or -regenerated with `gnoland secrets init --force` - -::: - -### 2. Generate the `genesis.json` - -:::info Where's the `genesis.json`? - -In this example, we are starting a completely new network. In case you are connecting to an existing network, you don't -need to regenerate the `genesis.json`, but simply fetch it from publicly available resources of the Gno chain you're -trying to connect to. - -::: - -The `genesis.json` defines the initial genesis state for the chain. It contains information like: - -- the current validator set -- any predeployed transactions -- any premined balanced - -When the chain starts, the first block will be produced after all the init content inside the `genesis.json` is -executed. - -Generating an empty `genesis.json` is relatively straightforward: - -```shell -gnogenesis generate -``` - -The resulting `genesis.json` is empty: - -```json -{ - "genesis_time": "2024-05-08T10:25:09Z", - "chain_id": "dev", - "consensus_params": { - "Block": { - "MaxTxBytes": "1000000", - "MaxDataBytes": "2000000", - "MaxBlockBytes": "0", - "MaxGas": "10000000", - "TimeIotaMS": "100" - }, - "Validator": { - "PubKeyTypeURLs": [ - "/tm.PubKeyEd25519" - ] - } - }, - "app_hash": null -} -``` - -This will generate a `genesis.json` in the calling directory, by default. To check all configurable options when -generating the `genesis.json`, you can run the command using the `--help` flag: - -```shell -gnogenesis generate --help - -USAGE - generate [flags] - -Generates a node's genesis.json based on specified parameters - -FLAGS - -block-max-data-bytes 2000000 the max size of the block data - -block-max-gas 10000000 the max gas limit for the block - -block-max-tx-bytes 1000000 the max size of the block transaction - -block-time-iota 100 the block time iota (in ms) - -chain-id dev the ID of the chain - -genesis-time 1715163944 the genesis creation time. Defaults to current time - -output-path ./genesis.json the output path for the genesis.json -``` - -## 3. Add the `examples` packages into the `genesis.json` (optional) - -This step is not necessarily required, however, using a gno.land chain without the `examples` packages predeployed can -present challenges with users who expect them to be present. - -The `examples` directory is located in the `$GNOROOT` location, or the local gno repository clone. - -```bash -gnogenesis txs add packages ./examples -``` - -### 4. Add the initial validator set - -A new Gno chain cannot advance without an active validator set. -Since this example follows starting a completely new Gno chain, you need to add at least one validator to the validator -set. - -Luckily, we've generated the node secrets in step #1 -- we will utilize the generated node key, so the process we start -locally will be the validator node for the new Gno network. - -To display the generated node key data, run the following command: - -```shell -gnoland secrets get validator_key -``` - -This will display the information we need for updating the `genesis.json`, in JSON: - -```shell -{ - "address": "g14j4dlsh3jzgmhezzp9v8xp7wxs4mvyskuw5ljl", - "pub_key": "gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqaqle3fdduqul4slg6zllypq9r8gj4wlfucy6qfnzmjcgqv675kxjz8jvk" -} -``` - -Updating the `genesis.json` is relatively simple, running the following command will add the generated node info to the -validator set: - -```shell -gnogenesis validator add \ ---address g14j4dlsh3jzgmhezzp9v8xp7wxs4mvyskuw5ljl \ ---pub-key gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqaqle3fdduqul4slg6zllypq9r8gj4wlfucy6qfnzmjcgqv675kxjz8jvk \ ---name Cuttlas -``` - -We can verify that the new validator was indeed added to the validator set: - -```json -{ - "genesis_time": "2024-05-08T10:25:09Z", - "chain_id": "dev", - "consensus_params": { - "Block": { - "MaxTxBytes": "1000000", - "MaxDataBytes": "2000000", - "MaxBlockBytes": "0", - "MaxGas": "10000000", - "TimeIotaMS": "100" - }, - "Validator": { - "PubKeyTypeURLs": [ - "/tm.PubKeyEd25519" - ] - } - }, - "validators": [ - { - "address": "g1lz2ez3ceeds9f6jllwy7u0hvkphuuv0plcc8pp", - "pub_key": { - "@type": "/tm.PubKeyEd25519", - "value": "AvaVf/cH84urHNuS1lo3DYmtEErxkTLRsrcr71QoAr4=" - }, - "power": "1", - "name": "Cuttlas" - } - ], - "app_hash": null -} -``` - -### 5. Starting the chain - -We have completed the main aspects of setting up a node: - -- generated the node directory (secrets and configuration) ✅ -- set the adequate configuration params ✅ -- generated a `genesis.json` ✅ -- added an initial validator set to the `genesis.json` ✅ - -Now, we can go ahead and start the Gno chain for the first time, by running: - -```shell -gnoland start \ ---genesis ./genesis.json \ ---data-dir ./gnoland-data -``` - -That's it! 🎉 - -Your new Gno node (chain) should be up and running: - -![gnoland start](../../assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif) - -## Chain runtime options - -### Changing the chain ID - -:::info Changing the Gno chain ID - -Below are some implications to consider when changing the chain ID: - -- it affects how the Gno node communicates with other Gno nodes / chains -- Gno clients that communicate through JSON-RPC need to match this value - -It's important to configure your node properly before launching it in a distributed network. -Keep in mind that changes may not be applicable once connected. - -::: - -To change the Gno chain ID, run the following command: - -```bash -gnoland start --chainid NewChainID -``` - -We can verify the chain ID has been changed, by fetching the status of the node and seeing the -associated chain ID. By default, the node exposes the JSON-RPC API on `http://127.0.0.1:26657`: - -```bash -curl -H "Content-type: application/json" -d '{ - "jsonrpc": "2.0", - "method": "status", - "params": [], - "id": 1 -}' 'http://127.0.0.1:26657' -``` - -We should get a response similar to this: - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "result": { - "node_info": { - "version_set": [ - // ... - ], - "net_address": "g10g9r37g9xa54a6clttzmhk2gmdkzsntzty0cvr@0.0.0.0:26656", - "network": "NewChainID" - // ... - } - } -} -``` - -:::danger Chain ID can be set only once - -Since the chain ID information is something bound to a chain, you can -only change it once upon chain initialization, and further attempts to change it will -have no effect. - -::: - -### Changing the node configuration - -You can specify a node configuration file using the `--config` flag. - -```bash -gnoland start --config config.toml -``` - -### Changing the premine list - -You do not need to use the `gno.land/genesis/genesis_balances.txt` file as the source of truth for initial network -funds. - -To specify a custom balance sheet for a fresh local chain, you can use the `-genesis-balances-file`: - -```bash -gnoland start -genesis-balances-file custom-balances.txt -``` - -Make sure the balances file follows the following format: - -```text -
=ugnot -``` - -Following this pattern, potential entries into the genesis balances file would look like: - -```text -g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt=10000000000ugnot -g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq=10000000000ugnot -``` - -:::info Genesis generation - -Genesis block generation happens only once during the lifetime of a Gno chain. -This means that if you specify a balances file using `gnoland start`, and the chain has already started (advanced from -block 0), the specified balance sheet will not be applied. - -::: diff --git a/docs/gno-tooling/cli/faucet/faucet.md b/docs/gno-tooling/cli/faucet/faucet.md deleted file mode 100644 index b069a19740a..00000000000 --- a/docs/gno-tooling/cli/faucet/faucet.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -id: running-a-faucet ---- - -# Running a Faucet - -## Overview - -In this tutorial, we will cover how to run a local native currency faucet that works seamlessly with a Gno node. -Using the faucet, any address can get a hold of native currency funds in case they -haven't [premined a balance beforehand](../../../gno-infrastructure/premining-balances.md). - -## Prerequisites - -- **`gnoland`, `gnofaucet` and `gnoweb` set up. Reference - the [Installation](../../../getting-started/local-setup/local-setup.md#3-installing-other-gno-tools) guide for steps** - -## 1. Ensure a topped-up faucet address - -The Gno faucet works by designating a single address as a faucet address that will distribute funds. - -Ensure the faucet account will have enough funds -by [premining its balance](../../../gno-infrastructure/premining-balances.md) to a high value. -In case you do not have an existing address added to `gnokey`, you can consult -the [Working with Key Pairs](../gnokey/working-with-key-pairs.md) guide. - -## 2. Start the local chain - -After ensuring the faucet address will have enough funds in the premine, we -can [run the local blockchain node](../../../gno-infrastructure/validators/setting-up-a-new-chain.md). -Navigate to the `gno.land` sub-folder and run the appropriate make command: - -```bash -cd gno.land -gnoland start -``` - -## 3. Start the faucet - -After the chain is up and running locally, you can start the faucet by running the following command: - -```bash -gnofaucet serve --chain-id dev MyKey -``` - -The command will prompt you to enter the decryption password for the key you've provided. - -- **`--chain-id`** - the chain ID of the local running node. The default value is `dev` -- **`MyKey`** - the name of the faucet key (you can also use the address) we premined in - the [previous steps](#1-ensure-a-topped-up-faucet-address) - -This will initialize the faucet to listen on port `5050`, by default. - -![gnofaucet serve](../../../assets/getting-started/local-setup/setting-up-funds/gnofaucet-serve.gif) - -## 4. Start the `gnoweb` interface - -To access the faucet UI, we need to start the local `gnoweb` interface. - -Navigate to the `gno.land` subfolder, and run the appropriate binary: - -```bash -cd gno.land -gnoweb -``` - -This will initialize the `gnoweb` interface on `http://127.0.0.1:8888`. - -![gnoweb](../../../assets/getting-started/local-setup/setting-up-funds/gnoweb.gif) - -## 5. Use the deployed faucet - -Once `gnoweb` has been started, you can navigate to http://127.0.0.1:8888/faucet. - -Simply input the desired address you wish to receive funds on (`1 GNOT` by default), and press the `GO` button. - -![gnofaucet page](../../../assets/getting-started/local-setup/setting-up-funds/faucet-page.png) - -After you've added the address, you should see a success message in the browser: - -``` -faucet success -``` - -In the terminal where `gnofaucet` is running, you should see a success message as well, something along the lines of: - -```bash -will deliver: {"msg":[{"@type":"/bank.MsgSend","from_address":"g155n659f89cfak0zgy575yqma64sm4tv6exqk99","to_address":"g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt","amount":"1000000ugnot"}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A10ufcOV5WP71K+KvLagJi+3TSCkx8EWKep3NbjVclU8"},"signature":"7Y0hkdPBruzMiANAHXWx3luAMhQN6SF3AQtstvOSZJI5P4uep8RIntw2c8W5blFiCd9HoMiEZFNf5dgWYwkjmA=="}],"memo":""} - -OK! -GAS WANTED: 50000 -GAS USED: 41971 -127.0.0.1 faucet success -``` - -## Conclusion - -That's it 🎉 - -You have successfully set up a GNOT faucet on a locally-running Gno chain! -Additionally, you have also learned how to utilize the `gnoweb` tool for a visual faucet UI. diff --git a/docs/gno-tooling/cli/faucet/gnofaucet.md b/docs/gno-tooling/cli/faucet/gnofaucet.md deleted file mode 100644 index fcbb6df1243..00000000000 --- a/docs/gno-tooling/cli/faucet/gnofaucet.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -id: gno-tooling-gnofaucet ---- - -# gnofaucet - -`gnofaucet` is a server for distributing GNOT, the gas currency of Gnoland, to specific addresses in a local chain. -Interact with the `gnofaucet` from an address with an empty balance in your locally built testnet to fuel it with GNOT -to pay for transactions. - -## Run `gnofaucet` Commands - -Enable the faucet using the following command. - -```bash -gnofaucet serve -``` - -#### **Options** - -| Name | Type | Description | -|---------------------------|---------|--------------------------------------------------------------------------------------| -| `chain-id` | String | The id of the chain (required). | -| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction (default: `50000`) | -| `gas-fee` | String | The gas fee to pay for the transaction. | -| `memo` | String | Any descriptive text (default: `""`) | -| `test-to` | String | Test address (optional) | -| `send` | String | Coins to send (default: `"1000000ugnot"`). | -| `captcha-secret` | String | The secret key for the recaptcha. If empty, the captcha is disabled (default: `""`). | -| `is-behind-proxy` | Boolean | Uses X-Forwarded-For IP for throttling (default: `false`). | -| `insecure-password-stdin` | Boolean | INSECURE! Takes password from stdin (default: `false`). | - -## Example - -### Step 1. Create an account named `test1` with the test seed phrase below. - -```bash -gnokey add test1 --recover -``` - -> **Test Seed Phrase:** source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate -> oppose farm nothing bullet exhibit title speed wink action roast -> **Test Private key:** ea97b9fddb7e6bf6867090a7a819657047949fbb9466d617f940538efd888605 -### **Step 2. Run `gnofaucet`** - -```bash -gnofaucet serve test1 --chain-id dev --send 500000000ugnot -``` - -### **Step 3. Receive GNOTs from the faucet** - -To receive funds through the `gnoweb` form GUI, you can request them on: -`http://localhost:8888/faucet` (given `http://localhost:8888/` is the location where `gnoweb` is serving pages). - -Alternatively, you can request funds from the faucet by directly invoking a CURL command: - -```bash -curl --location --request POST 'http://localhost:5050' \ ---header 'Content-Type: application/x-www-form-urlencoded' \ ---data-urlencode 'toaddr={address to receive}' -``` diff --git a/docs/gno-tooling/cli/gno.md b/docs/gno-tooling/cli/gno.md deleted file mode 100644 index bcbb0978964..00000000000 --- a/docs/gno-tooling/cli/gno.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -id: gno-tooling-gno ---- - -# gno - -`gno` is a handy tool for developing and prototyping Gno packages and realms. You may use `gno` to use the GnoVM without an actual blockchain to build or test realms in a local environment. - -## Run `gno` Commands - -The following command will run `gno`. - -```bash -gno {SUB_COMMAND} -``` - -**Subcommands** - -| Name | Description | -| ------------ | ------------------------------------------ | -| `test` | Tests a gno package. | -| `transpile` | Transpiles a `.gno` file to a `.go` file. | -| `repl` | Starts a GnoVM REPL. | - -### `test` - -#### **Options** - -| Name | Type | Description | -| ------------ | ------------- | ------------------------------------------------------------------ | -| `v` | Boolean | Displays verbose output. | -| `root-dir` | String | Clones location of github.com/gnolang/gno (gno tries to guess it). | -| `run` | String | Test name filtering pattern. | -| `timeout` | time.Duration | The maximum execution time in ns. | -| `transpile` | Boolean | Transpiles a `.gno` file to a `.go` file before testing. | - -### `transpile` - -#### **Options** - -| Name | Type | Description | -| ----------- | ------- | --------------------------------------------------------------- | -| `v` | Boolean | Displays verbose output. | -| `skip-fmt` | Boolean | Skips the syntax checking of generated `.go` files. | -| `gobuild` | Boolean | Run `go build` on generated `.go` files, ignoring test files. | -| `go-binary` | String | The go binary to use for building (default: `go`). | -| `gofmt` | String | The gofmt binary to use for syntax checking (default: `gofmt`). | -| `output` | String | The output directory (default: `.`). | - -### `repl` - -#### **Options** - -| Name | Type | Description | -| ---------- | ------- | ------------------------------------------------------------------ | -| `root-dir` | String | Clones location of github.com/gnolang/gno (gno tries to guess it). | diff --git a/docs/gno-tooling/cli/gnodev.md b/docs/gno-tooling/cli/gnodev.md deleted file mode 100644 index f9491fea803..00000000000 --- a/docs/gno-tooling/cli/gnodev.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -id: gno-tooling-gnodev ---- - -# gnodev - -Gnodev allows for quick and efficient development of Gno code. - -By watching your development directory, gnodev detects changes in your Gno -code, reflecting them in the state of the node immediately. Gnodev also runs a -local instance of `gnoweb`, allowing you to see the rendering of your Gno code instantly. - -## Features -- **In-Memory Node**: Gnodev starts an in-memory node, and automatically loads - the **examples** folder and any user-specified paths. -- **Web Interface Server**: Gnodev automatically starts a `gnoweb` server on - [`localhost:8888`](https://localhost:8888). -- **Balances and Keybase Customization**: Users can set account balances, load them from a file, or add new - accounts via a flag. -- **Hot Reload**: Gnodev monitors the **examples** folder, as well as any folder specified as an argument for - file changes, reloading and automatically restarting the node as needed. -- **State Maintenance**: Gnodev replays all transactions in between reloads, - ensuring the previous node state is preserved. -- **Transaction Manipulation**: Gnodev adds the capability to cancel and redo transactions interactively. -- **State Export:** Export the current state at any time in a genesis doc format. - -## Installation - -Gnodev can be found in the `contribs` folder in the monorepo. -To install `gnodev`, run `make install`. - -## Usage -Gnodev can be run from anywhere on the machine it was installed on, and it will -automatically load the examples folder, providing all the packages and realms found in it for use. - -![gnodev_usage](../../assets/gno-tooling/gnodev/gnodev.gif) - -For hot reloading, `gnodev` watches the examples folder, as well as any specified folder: -``` -gnodev ./myrealm -``` - -## Keybase and Balance - -Gnodev will, by default, load the keybase located in your GNOHOME directory, pre-mining `10e12` amount of -ugnot to all of them. This way, users can interact with Gnodev's in-memory node out of the box. The addresses -and their respective balance can be shown at runtime by pressing `A` to display accounts interactively. - -### Adding or Updating Accounts - -Utilize the `--add-account` flag to add a new account or update an existing one in your local Keybase, -following the format `[:]`. The `` represents the specific key name or -address, and `` is an optional limitation on the account. - -Example of use: - -``` -gnodev --add-account [:] --add-account [:] ... -``` - -Please note: If the address exists in your local Keybase, the `--add-account` flag will only update its amount, -instead of creating a duplicate. - -### Balance file - -You can specify a balance file using `--balance-file`. The file should contain a -list of Bech32 addresses with their respective amounts: - -``` -# Accounts: -g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5=10000000000000ugnot # test1 -g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj=10000000000000ugnot # test2 - -# ... -``` - -### Transactions file - -You can specify a transactions file using `--txs-file`. The file should contain a list of signed transactions -that will be applied when starting the in-memory node. -``` -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post1","First post","Lorem Ipsum","2022-05-20T13:17:22Z","","tag1,tag2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} -{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post2","Second post","Lorem Ipsum","2022-05-20T13:17:23Z","","tag1,tag3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""} -``` - -#### Construct the transaction -`gnokey maketx ... >> "tx-file.json"` - -#### Signing the transaction -`gnokey sign -tx-path tx-file.json ...` - -### Deploy - -All realms and packages will be deployed to the in-memory node by the address passed in with the -`--deploy-key` flag. The `deploy-key` address can be changed for a specific package or realm by passing in -the desired address (or a known key name) using with the following pattern: - -``` -gnodev ./myrealm?creator=g1.... -``` - -A specific deposit amount can also be set with the following pattern: - -``` -gnodev ./myrealm?deposit=42ugnot -``` - -This pattern can be expanded to accommodate both options: - -``` -gnodev ./myrealm?creator=&deposit= -``` - -## Interactive Usage - -While `gnodev` is running, the following shortcuts are available: -- To see help, press `H`. -- To display accounts balances, press `A`. -- To reload manually, press `R`. -- To cancel the last action, press `P`. -- To redo the last cancelled action, press `N`. -- To save the current state, press `Ctrl+S`. -- To restore the saved state, press `Ctrl+R`. -- To export the current state to a genesis file, press `E`. -- To reset the state of the node, press `CMD+R`. -- To stop `gnodev`, press `CMD+C`. - -### Options - -| Flag | Effect | -|---------------------|-----------------------------------------------------------------------| -| --minimal | Start `gnodev` without loading the examples folder. | -| --no-watch | Disable hot reload. | -| --add-account | Pre-add account(s) in the form `[=]` | -| --balances-file | Load a balance for the user(s) from a balance file. | -| --chain-id | Set node ChainID | -| --deploy-key | Default key name or Bech32 address for uploading packages. | -| --home | Set the path to load user's Keybase. | -| --max-gas | Set the maximum gas per block | -| --no-replay | Do not replay previous transactions upon reload | -| --node-rpc-listener | listening address for GnoLand RPC node | -| --root | gno root directory | -| --server-mode | disable interaction, and adjust logging for server use. | -| --verbose | enable verbose output for development | -| --web-listener | web server listening address | -| --web-help-remote | web server help page's remote addr - defaults to | -| --genesis-file | Load and extract transactions from a genesis file | - diff --git a/docs/gno-tooling/cli/gnokey/full-security-tx.md b/docs/gno-tooling/cli/gnokey/full-security-tx.md deleted file mode 100644 index bccddb30b8a..00000000000 --- a/docs/gno-tooling/cli/gnokey/full-security-tx.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -id: full-security-tx ---- - -# Making an airgapped transaction - -## Prerequisites - -- **`gnokey` installed.** Reference the - [Local Setup](../../../getting-started/local-setup/installation.md#2-installing-the-required-tools) guide for steps - -## Overview - -`gnokey` provides a way to create a transaction, sign it, and later -broadcast it to a chain in the most secure fashion. This approach, while more -complicated than the standard approach shown [in a previous tutorial](./state-changing-calls.md), -grants full control and provides [airgap](https://en.wikipedia.org/wiki/Air_gap_(networking)) -support. - -By separating the signing and the broadcasting steps of submitting a transaction, -users can make sure that the signing happens in a secure, offline environment, -keeping private keys away from possible exposure to attacks coming from the -internet. - -The intended purpose of this functionality is to provide maximum security when -signing and broadcasting a transaction. In practice, this procedure should take -place on two separate machines controlled by the holder of the keys, one with -access to the internet (`Machine A`), and the other one without (`Machine B`), -with the separation of steps as follows: -1. `Machine A`: Fetch account information from the chain -2. `Machine B`: Create an unsigned transaction locally -3. `Machine B`: Sign the transaction -4. `Machine A`: Broadcast the transaction - -## 1. Fetching account information from the chain - -First, we need to fetch data for the account we are using to sign the transaction, -using the [auth/accounts](./querying-a-network.md#authaccounts) query: - -```bash -gnokey query auth/accounts/ -remote "https://rpc.gno.land:443" -``` - -We need to extract the account number and sequence from the output: - -```bash -height: 0 -data: { - "BaseAccount": { - "address": "g1zzqd6phlfx0a809vhmykg5c6m44ap9756s7cjj", - "coins": "10000000ugnot", - "public_key": null, - "account_number": "468", - "sequence": "0" - } -} -``` - -In this case, the account number is `468`, and the sequence (nonce) is `0`. We -will need these values to sign the transaction later. These pieces of information -are crucial during the signing process, as they are included in the signature -of the transaction, preventing replay attacks. - -## 2. Creating an unsigned transaction locally - -To create the transaction you want, you can use the [`call` API](./state-changing-calls.md#call), -without the `-broadcast` flag, while redirecting the output to a local file: - -```bash -gnokey maketx call \ --pkgpath "gno.land/r/demo/userbook" \ --func "SignUp" \ --gas-fee 1000000ugnot \ --gas-wanted 2000000 \ -mykey > userbook.tx -``` - -This will create a `userbook.tx` file with a null `signature` field. -Now we are ready to sign the transaction. - -## 3. Signing the transaction - -To add a signature to the transaction, we can use the `gnokey sign` subcommand. -To sign, we must set the correct flags for the subcommand: -- `-tx-path` - path to the transaction file to sign, in our case, `userbook.tx` -- `-chainid` - id of the chain to sign for -- `-account-number` - number of the account fetched previously -- `-account-sequence` - sequence of the account fetched previously - -```bash -gnokey sign \ --tx-path userbook.tx \ --chainid "portal-loop" \ --account-number 468 \ --account-sequence 0 \ -mykey -``` - -After inputting the correct values, `gnokey` will ask for the password to decrypt -the keypair. Once we input the password, we should receive the message that the -signing was completed. If we open the `userbook.tx` file, we will be able to see -that the signature field has been populated. - -We are now ready to broadcast this transaction to the chain. - -## 4. Broadcasting the transaction - -To broadcast the signed transaction to the chain, we can use the `gnokey broadcast` -subcommand, giving it the path to the signed transaction: - -```bash -gnokey broadcast -remote "https://rpc.gno.land:443" userbook.tx -``` - -In this case, we do not need to specify a keypair, as the transaction has already -been signed in a previous step and `gnokey` is only sending it to the RPC endpoint. - -## Verifying a transaction's signature - -To verify a transaction's signature is correct, you can use the `gnokey verify` -subcommand. We can provide the path to the transaction document using the `-docpath` -flag, provide the key we signed the transaction with, and the signature itself. -Make sure the signature is in the `hex` format. - -```bash -gnokey verify -docpath userbook.tx mykey -``` - -## Conclusion - -That's it! 🎉 - -In this tutorial, you've learned to use `gnokey` for creating maximum-security -transactions in an airgapped manner. diff --git a/docs/gno-tooling/cli/gnokey/gnokey.md b/docs/gno-tooling/cli/gnokey/gnokey.md deleted file mode 100644 index 7344f9b539c..00000000000 --- a/docs/gno-tooling/cli/gnokey/gnokey.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -id: gnokey ---- - -# `gnokey` - -## Overview - -In this section, you will learn how to use the `gnokey` binary. `gnokey` is the -gno.land CLI keychain and client, and it allows you to do 4 main things: -- Manage Gno keypairs -- Send state-changing calls (transactions) -- Query a gno.land network -- Sign and broadcast transactions with [airgap protection](https://en.wikipedia.org/wiki/Air_gap_(networking)) - -Check out the rest of this section to learn how to do all of these. diff --git a/docs/gno-tooling/cli/gnokey/querying-a-network.md b/docs/gno-tooling/cli/gnokey/querying-a-network.md deleted file mode 100644 index 1bb1bb8275f..00000000000 --- a/docs/gno-tooling/cli/gnokey/querying-a-network.md +++ /dev/null @@ -1,233 +0,0 @@ ---- -id: querying-a-network ---- - -# Querying a gno.land network - -## Prerequisites - -- **`gnokey` installed.** Reference the - [Local Setup](../../../getting-started/local-setup/installation.md#2-installing-the-required-tools) guide for steps - -## Overview - -gno.land and `gnokey` support ABCI queries. Using ABCI queries, you can query the state of -a gno.land network without spending any gas. All queries need to be pointed towards -a specific remote address from which the state will be retrieved. - -To send ABCI queries, you can use the `gnokey query` subcommand, and provide it -with the appropriate query. The `query` subcommand allows us to send different -types of queries to a gno.land network. - -Below is a list of queries a user can make with `gnokey`: -- `auth/accounts/{ADDRESS}` - returns information about an account -- `bank/balances/{ADDRESS}` - returns balances of an account -- `vm/qfuncs` - returns the exported functions for a given pkgpath -- `vm/qfile` - returns package contents for a given pkgpath -- `vm/qeval` - evaluates an expression in read-only mode on and returns the results -- `vm/qrender` - shorthand for evaluating `vm/qeval Render("")` for a given pkgpath - -Let's see how we can use them. - -## `auth/accounts` - -We can obtain information about a specific address using this subquery. To call it, -we can run the following command: - -```bash -gnokey query auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -remote https://rpc.gno.land:443 -``` - -With this, we are asking the Portal Loop network to deliver information about the -specified address. If everything went correctly, we should get output similar to the following: - -```bash -height: 0 -data: { - "BaseAccount": { - "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - "coins": "227984898927ugnot", - "public_key": { - "@type": "/tm.PubKeySecp256k1", - "value": "A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y" - }, - "account_number": "0", - "sequence": "12" - } -} -``` - -The return data will contain the following fields: -- `height` - the height at which the query was executed. This is currently not - supported and is `0` by default. -- `data` - contains the result of the query. - -The `data` field returns a `BaseAccount`, which is the main struct used in [TM2](../../../concepts/tendermint2.md) -to hold account data. It contains the following information: -- `address` - the address of the account -- `coins` - the list of coins the account owns -- `public_key` - the TM2 public key of the account, from which the address is derived -- `account_number` - a unique identifier for the account on the gno.land chain -- `sequence` - a nonce, used for protection against replay attacks - -## `bank/balances` - -With this query, we can fetch [coin](../../../concepts/stdlibs/coin.md) balances -of a specific account. To call it, we can run the following command: - -```bash -gnokey query bank/balances/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -remote https://rpc.gno.land:443 -``` - -If everything went correctly, we should get an output similar to the following: - -```bash -height: 0 -data: "227984898927ugnot" -``` - -The data field will contain the coins the address owns. - -## `vm/qfuncs` - -Using the `vm/qfuncs` query, we can fetch exported functions from a specific package -path. To specify the path we want to query, we can use the `-data` flag: - -```bash -gnokey query vm/qfuncs --data "gno.land/r/demo/wugnot" -remote https://rpc.gno.land:443 -``` - -The output is a string containing all exported functions for the `wugnot` realm: - -```json -height: 0 -data: [ - { - "FuncName": "Deposit", - "Params": null, - "Results": null - }, - { - "FuncName": "Withdraw", - "Params": [ - { - "Name": "amount", - "Type": "uint64", - "Value": "" - } - ], - "Results": null - }, - // other functions -] -``` - -## `vm/qfile` - -With the `vm/qfile` query, we can fetch files and their content found on a -specific package path. To specify the path we want to query, we can use the -`-data` flag: - -```bash -gnokey query vm/qfile -data "gno.land/r/demo/wugnot" -remote https://rpc.gno.land:443 -``` - -If the `-data` field contains only the package path, the output is a list of all -files found within the `wugnot` realm: - -```bash -height: 0 -data: gno.mod -wugnot.gno -z0_filetest.gno -``` - -If the `-data` field also specifies a file name after the path, the source code -of the file will be retrieved: - -```bash -gnokey query vm/qfile -data "gno.land/r/demo/wugnot/wugnot.gno" -remote https://rpc.gno.land:443 -``` - -Output: -```bash -height: 0 -data: package wugnot - -import ( - "std" - "strings" - - "gno.land/p/demo/grc/grc20" - "gno.land/p/demo/ufmt" - pusers "gno.land/p/demo/users" - "gno.land/r/demo/users" -) - -var ( - banker *grc20.Banker = grc20.NewBanker("wrapped GNOT", "wugnot", 0) - Token = banker.Token() -) - -const ( - ugnotMinDeposit uint64 = 1000 - wugnotMinDeposit uint64 = 1 -) -... -``` - -## `vm/qeval` - -`vm/qeval` allows us to evaluate a call to an exported function without using gas, -in read-only mode. For example: - -```bash -gnokey query vm/qeval -remote https://rpc.gno.land:443 -data "gno.land/r/demo/wugnot.BalanceOf(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")" -``` - -This command will return the `wugnot` balance of the above address without using gas. -Properly escaping quotation marks for string arguments is currently required. - -Currently, `vm/qeval` only supports primitive types in expressions. - -## `vm/qrender` - -`vm/qrender` is an alias for executing `vm/qeval` on the `Render("")` function. -We can use it like this: - -```bash -gnokey query vm/qrender --data "gno.land/r/demo/wugnot:" -remote https://rpc.gno.land:443 -``` - -Running this command will display the current `Render()` output of the WUGNOT -realm, which is also displayed by default on the [realm's page](https://gno.land/r/demo/wugnot): - -```bash -height: 0 -data: # wrapped GNOT ($wugnot) - -* **Decimals**: 0 -* **Total supply**: 5012404 -* **Known accounts**: 2 -``` - -:::info Specifying a path to `Render()` - -To call the `vm/qrender` query with a specific path, use the `:` syntax. -For example, the `wugnot` realm provides a way to display the balance of a specific -address in its `Render()` function. We can fetch the balance of an account by -providing the following custom pattern to the `wugnot` realm: - -```bash -gnokey query vm/qrender --data "gno.land/r/demo/wugnot:balance/g125em6arxsnj49vx35f0n0z34putv5ty3376fg5" -remote https://rpc.gno.land:443 -``` - -To see how this was achieved, check out `wugnot`'s `Render()` function. -::: - -## Conclusion - -That's it! 🎉 - -In this tutorial, you've learned to use `gnokey` to query a gno.land -network. diff --git a/docs/gno-tooling/cli/gnokey/state-changing-calls.md b/docs/gno-tooling/cli/gnokey/state-changing-calls.md deleted file mode 100644 index 79a777cca51..00000000000 --- a/docs/gno-tooling/cli/gnokey/state-changing-calls.md +++ /dev/null @@ -1,466 +0,0 @@ ---- -id: state-changing-calls ---- - -# Making state-changing calls (transactions) - -## Prerequisites - -- **`gnokey` installed.** Reference the - [Local Setup](../../../getting-started/local-setup/installation.md#2-installing-the-required-tools) guide for steps - -## Overview - -In Gno, there are four types of messages that can change on-chain state: -- `AddPackage` - adds new code to the chain -- `Call` - calls a specific path and function on the chain -- `Send` - sends coins from one address to another -- `Run` - executes a Gno script against on-chain code - -A gno.land transaction contains two main things: -- A base configuration where variables such as `gas-fee`, `gas-wanted`, and others - are defined -- A list of messages to execute on the chain - -Currently, `gnokey` supports single-message transactions, while multiple-message -transactions can be created in Go programs, supported by the -[gnoclient](../../../reference/gnoclient/gnoclient.md) package. - -We will need some testnet coins (GNOTs) for each state-changing call. Visit the [Faucet -Hub](https://faucet.gno.land) to get GNOTs for the Gno testnets that are currently live. - -Let's delve deeper into each of these message types. - -## `AddPackage` - -In case you want to upload new code to the chain, you can use the `AddPackage` -message type. You can send an `AddPackage` transaction with `gnokey` using the -following command: - -```bash -gnokey maketx addpkg -``` - -To understand how to use this subcommand better, let's write a simple "Hello world" -[pure package](../../../concepts/packages.md). First, let's create a folder which will -store our example code. - -```bash -└── example/ -``` - -Then, let's create a `hello_world.gno` file under the `p/` folder: - -```bash -cd example -mkdir p/ && cd p -touch hello_world.gno -``` - -Now, we should have the following folder structure: - -```bash -└── example/ -│ └── p/ -│ └── hello_world.gno -``` - -In the `hello_world.gno` file, add the following code: - -```go -package hello_world - -func Hello() string { - return "Hello, world!" -} -``` - -We are now ready to upload this package to the chain. To do this, we must set the -correct flags for the `addpkg` subcommand. - -The `addpkg` subcommmand uses the following flags and arguments: -- `-pkgpath` - on-chain path where your code will be uploaded to -- `-pkgdir` - local path where your is located -- `-broadcast` - enables broadcasting the transaction to the chain -- `-send` - a deposit amount of GNOT to send along with the transaction -- `-gas-wanted` - the upper limit for units of gas for the execution of the - transaction -- `-gas-fee` - amount of GNOTs to pay per gas unit -- `-chain-id` - id of the chain that we are sending the transaction to -- `-remote` - specifies the remote node RPC listener address - -The `-pkgpath` and `-pkgdir` flags are unique to the `addpkg` subcommand, while -`-broadcast`,`-send`, `-gas-wanted`, `-gas-fee`, `-chain-id`, and `-remote` are -used for setting the base transaction configuration. These flags will be repeated -throughout the tutorial. - -Next, let's configure the `addpkg` subcommand to publish this package to the -[Portal Loop](../../../concepts/portal-loop.md) testnet. Assuming we are in -the `example/p/` folder, the command will look like this: - -```bash -gnokey maketx addpkg \ --pkgpath "gno.land/p//hello_world" \ --pkgdir "." \ --send "" \ --gas-fee 10000000ugnot \ --gas-wanted 8000000 \ --broadcast \ --chainid portal-loop \ --remote "https://rpc.gno.land:443" -``` - -Once we have added a desired [namespace](../../../concepts/namespaces.md) to upload the package to, we can specify -a keypair name to use to execute the transaction: - -```bash -gnokey maketx addpkg \ --pkgpath "gno.land/p/examplenamespace/hello_world" \ --pkgdir "." \ --send "" \ --gas-fee 10000000ugnot \ --gas-wanted 200000 \ --broadcast \ --chainid portal-loop \ --remote "https://rpc.gno.land:443" -mykey -``` - -If the transaction was successful, you will get output from `gnokey` that is similar to the following: - -``` -OK! -GAS WANTED: 200000 -GAS USED: 117564 -HEIGHT: 3990 -EVENTS: [] -TX HASH: Ni8Oq5dP0leoT/IRkKUKT18iTv8KLL3bH8OFZiV79kM= -``` - -Let's analyze the output, which is standard for any `gnokey` transaction: -- `GAS WANTED: 200000` - the original amount of gas specified for the transaction -- `GAS USED: 117564` - the gas used to execute the transaction -- `HEIGHT: 3990` - the block number at which the transaction was executed at -- `EVENTS: []` - [Gno events](../../../concepts/stdlibs/events.md) emitted by the transaction, in this case, none -- `TX HASH: Ni8Oq5dP0leoT/IRkKUKT18iTv8KLL3bH8OFZiV79kM=` - the hash of the transaction - -Congratulations! You have just uploaded a pure package to the Portal Loop network. -If you wish to deploy to a different network, find the list of all network -configurations in the [Network Configuration](../../../reference/network-config.md) section. - -## `Call` - -The `Call` message type is used to call any exported realm function. -You can send a `Call` transaction with `gnokey` using the following command: - -```bash -gnokey maketx call -``` - -:::info `Call` uses gas - -Using `Call` to call an exported function will use up gas, even if the function -does not modify on-chain state. If you are calling such a function, you can use -the [`query` functionality](./querying-a-network.md) for a read-only call which -does not use gas. - -::: - -For this example, we will call the `wugnot` realm, which wraps GNOTs to a -GRC20-compatible token called `wugnot`. We can find this realm deployed on the -[Portal Loop](../../../concepts/portal-loop.md) testnet, under the `gno.land/r/demo/wugnot` path. - -We will wrap `1000ugnot` into the equivalent in `wugnot`. To do this, we can call -the `Deposit()` function found in the `wugnot` realm. As previously, we will -configure the `maketx call` subcommand: - -```bash -gnokey maketx call \ --pkgpath "gno.land/r/demo/wugnot" \ --func "Deposit" \ --send "1000ugnot" \ --gas-fee 10000000ugnot \ --gas-wanted 2000000 \ --broadcast \ --chainid portal-loop \ --remote "https://rpc.gno.land:443" \ -mykey -``` - -In this command, we have specified three main things: -- The path where the realm lives on-chain with the `-pkgpath` flag -- The function that we want to call on the realm with the `-func` flag -- The amount of `ugnot` we want to send to be wrapped, using the `-send` flag - -Apart from this, we have also specified the Portal Loop chain ID, `portal-loop`, -as well as the Portal Loop remote address, `https://rpc.gno.land:443`. - -After running the command, we can expect an output similar to the following: -```bash -OK! -GAS WANTED: 2000000 -GAS USED: 489528 -HEIGHT: 24142 -EVENTS: [{"type":"Transfer","attrs":[{"key":"from","value":""},{"key":"to","value":"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5"},{"key":"value","value":"1000"}],"pkg_path":"gno.land/r/demo/wugnot","func":"Mint"}] -TX HASH: Ni8Oq5dP0leoT/IRkKUKT18iTv8KLL3bH8OFZiV79kM= -``` - -In this case, we can see that the `Deposit()` function emitted an -[event](../../../concepts/stdlibs/events.md) that tells us more about what -happened during the transaction. - -After broadcasting the transaction, we can verify that we have the amount of `wugnot` we expect. We -can call the `BalanceOf()` function in the same realm: - -```bash -gnokey maketx call \ --pkgpath "gno.land/r/demo/wugnot" \ --func "BalanceOf" \ --args "" \ --gas-fee 10000000ugnot \ --gas-wanted 2000000 \ --broadcast \ --chainid portal-loop \ --remote "https://rpc.gno.land:443" \ -mykey -``` - -If everything was successful, we should get something similar to the following -output: - -``` -(1000 uint64) - -OK! -GAS WANTED: 2000000 -GAS USED: 396457 -HEIGHT: 64839 -EVENTS: [] -TX HASH: gQP9fJYrZMTK3GgRiio3/V35smzg/jJ62q7t4TLpdV4= -``` - -At the top, you will see the output of the transaction, specifying the value and -type of the return argument. - -In this case, we used `maketx call` to call a read-only function, which simply -checks the `wugnot` balance of a specific address. This is discouraged, as -`maketx call` actually uses gas. To call a read-only function without spending gas, -check out the `vm/qeval` query in the [Querying a network](./querying-a-network.md#vmqeval) section. - -## `Send` - -We can use the `Send` message type to access the TM2 [Banker](../../../concepts/stdlibs/banker.md) -directly and transfer coins from one Gno address to another. - -Coins, such as GNOTs, are always formatted in the following way: - -``` - -100ugnot -``` - -For this example, let's transfer some GNOTs. Just like before, we can configure -our `maketx send` subcommand: -```bash -gnokey maketx send \ --to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 \ --send 100ugnot \ --gas-fee 10000000ugnot \ --gas-wanted 2000000 \ --broadcast \ --chainid portal-loop \ --remote "https://rpc.gno.land:443" \ -mykey -``` - -Here, we have set the `-to` & `-send` flags to match the recipient, in this case -the publicly-known `test1` address, and `100ugnot` for the coins we want to send, -respectively. - -To check the balance of a specific address, check out the `bank/balances` query -in the [Querying a network](./querying-a-network.md#bankbalances) section. - -## `Run` - -With the `Run` message, you can write a snippet of Gno code and run it against -code on the chain. For this example, we will use the [Userbook realm](https://gno.land/r/demo/userbook), -which simply allows you to register the fact that you have interacted with it. -It contains a simple `SignUp()` function, which we will call with `Run`. - -To understand how to use the `Run` message better, let's write a simple `script.gno` -file. First, create a folder which will store our script. - -```bash -└── example/ -``` - -Then, let's create a `script.gno` file: - -```bash -cd example -touch script.gno -``` - -Now, we should have the following folder structure: - -```bash -└── example/ -│ └── script.gno -``` - -In the `script.gno` file, first define the package to be `main`. Then we can import -the Userbook realm and define a `main()` function with no return values which will -be automatically detected and run. In it, we can call the `SignUp()` function. - -```go -package main - -import "gno.land/r/demo/userbook" - -func main() { - println(userbook.SignUp()) -} -``` - -Now we will be able to provide this to the `maketx run` subcommand: -```bash -gnokey maketx run \ --gas-fee 1000000ugnot \ --gas-wanted 20000000 \ --broadcast \ --chainid portal-loop \ --remote "https://rpc.gno.land:443" \ -mykey ./script.gno -``` - -After running this command, the chain will execute the script and apply any state -changes. Additionally, by using `println`, which is only available in the `Run` -& testing context, we will be able to see the return value of the function called. - -### The power of `Run` - -Specifically, the above example could have been replaced with a simple `maketx call` -call. The full potential of run comes out in three specific cases: -1. Calling realm functions multiple times in a loop -2. Calling functions with non-primitive input arguments -3. Calling methods on exported variables - -Let's look at each of these cases in detail. To demonstrate, we'll make a call -to the following example realm: - -```go -package foo - -import "gno.land/p/demo/ufmt" - -var ( - MainFoo *Foo - foos []*Foo -) - -type Foo struct { - bar string - baz int -} - -func init() { - MainFoo = &Foo{bar: "mainBar", baz: 0} -} - -func (f *Foo) String() string { - return ufmt.Sprintf("Foo - (bar: %s) - (baz: %d)\n\n", f.bar, f.baz) -} - -func NewFoo(bar string, baz int) *Foo { - return &Foo{bar: bar, baz: baz} -} - -func AddFoos(multipleFoos []*Foo) { - foos = append(foos, multipleFoos...) -} - -func Render(_ string) string { - var output string - - for _, f := range foos { - output += f.String() - } - - return output -} -``` - -This realm is deployed to [`gno.land/r/docs/examples/run/foo`](https://gno.land/r/docs/examples/run/foo/package.gno) -on the Portal Loop testnet. - -1. Calling realm functions multiple times in a loop: -```go -package main - -import ( - "gno.land/r/docs/examples/run/foo" -) - -func main() { - for i := 0; i < 5; i++ { - println(foo.Render("")) - } -} -``` - -2. Calling functions with non-primitive input arguments: - -Currently, `Call` only supports primitives for arguments. With `Run`, these -limitations are removed; we can execute a function that takes in a struct, array, -or even an array of structs. - -We are unable to call `AddFoos()` with the `Call` message type, while with `Run`, -we can: - -```go -package main - -import ( - "strconv" - - "gno.land/r/docs/examples/run/foo" -) - -func main() { - var multipleFoos []*foo.Foo - - for i := 0; i < 5; i++ { - newFoo := foo.NewFoo( - "bar"+strconv.Itoa(i), - i, - ) - - multipleFoos = append(multipleFoos, newFoo) - } - - foo.AddFoos(multipleFoos) -} - -``` - -3. Calling methods on exported variables: - -```go -package main - -import "gno.land/r/docs/examples/run/foo" - -func main() { - println(foo.MainFoo.String()) -} -``` - -Finally, we can call methods that are on top-level objects in case they exist, -which is not currently possible with the `Call` message. - -## Conclusion - -That's it! 🎉 - -In this tutorial, you've learned to use `gnokey` for sending multiple types of -state-changing calls to a gno.land chain. diff --git a/docs/gno-tooling/cli/gnokey/working-with-key-pairs.md b/docs/gno-tooling/cli/gnokey/working-with-key-pairs.md deleted file mode 100644 index 9bc29da6a18..00000000000 --- a/docs/gno-tooling/cli/gnokey/working-with-key-pairs.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -id: working-with-key-pairs ---- - -# Working with Key Pairs - -## Overview - -In this tutorial, you will learn how to manage private user keys, which are -required for interacting with the gno.land blockchain. You will understand what -mnemonics are, how they are used, and how you can make interaction seamless with -Gno. - -## Prerequisites - -- **`gnokey` installed.** Reference the - [Local Setup](../../../getting-started/local-setup/installation.md#2-installing-the-required-tools) guide for steps - -## Listing available keys -`gnokey` works by creating a local directory in the filesystem for storing -(encrypted!) user private keys. - -You can find this repository by checking the value of the `--home` flag when -running the following command: - -```bash -gnokey --help -``` - -Example output: - -```bash -USAGE - [flags] [...] - -gno.land keychain & client - -SUBCOMMANDS - add adds key to the keybase - delete deletes a key from the keybase - rotate rotate the password of a key in the keybase to a new password - generate generates a bip39 mnemonic - export exports private key armor - import imports encrypted private key armor - list lists all keys in the keybase - sign signs the given tx document and saves it to disk - verify verifies the document signature - query makes an ABCI query - broadcast broadcasts a signed document - maketx composes a tx document to sign - -FLAGS - -config ... config file (optional) - -home $XDG_CONFIG/gno home directory - -insecure-password-stdin=false WARNING! take password from stdin - -quiet=false suppress output during execution - -remote 127.0.0.1:26657 remote node URL -``` - -In this example, the directory where `gnokey` will store working data -is `/Users/zmilos/Library/Application Support/gno`. - -Keep note of this directory, in case you need to reset the keystore, or migrate -it for some reason. -You can provide a specific `gnokey` working directory using the `--home` flag. - -To list keys currently present in the keystore, we can run: - -```bash -gnokey list -``` - -In case there are no keys present in the keystore, the command will simply -return an empty response. -Otherwise, it will return the list of keys and their accompanying metadata as a -list, for example: - -```bash -0. Manfred (local) - addr: g15uk9d6feap7z078ttcnwc94k60ullrvhmynxjt pub: gpub1pgfj7ard9eg82cjtv4u4xetrwqer2dntxyfzxz3pqvn87u43scec4zfgn4la3nt237nehzydzayqxe43fx63lq6rty9c5almet4, path: -1. Milos (local) - addr: g15lppu0tuxets0c0t80tncs4enqzgxt7v4eftcj pub: gpub1pgfj7ard9eg82cjtv4u4xetrwqer2dntxyfzxz3pqw2kkzujprgrfg7vumg85mccsf790n5ep6htpygkuwedwuumf2g7ydm4vqf, path: -``` - -The key response consists of a few pieces of information: - -- The name of the private key -- The derived address (`addr`) -- The public key (`pub`) - -Using these pieces of information, we can interact with gno.land tools and write -blockchain applications. - -## Generating a BIP39 mnemonic - -Using `gnokey`, we can generate a [mnemonic phrase](https://en.bitcoin.it/wiki/Seed_phrase) based on -the [BIP39 standard](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). - -To generate the mnemonic phrase in the console, you can run: - -```bash -gnokey generate -``` - -![gnokey generate](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-generate.gif) - -## Adding a random private key -If we wanted to add a new private key to the keystore, we can run the following -command: - -```bash -gnokey add MyKey -``` - -Of course, you can replace `MyKey` with whatever name you want for your key. - -The `gnokey` tool will prompt you to enter a password to encrypt the key on disk -(don't forget this!). -After you enter the password, the `gnokey` tool will add the key to the keystore, -and return the accompanying [mnemonic phrase](https://en.bitcoin.it/wiki/Seed_phrase), which you should remember -somewhere if you want to recover the key at a future point in time. - -![gnokey add random](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-random.gif) - -You can check that the key was indeed added to the keystore, by listing available -keys: - -```bash -gnokey list -``` - -![gnokey list](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-list.gif) - -## Adding a private key using a mnemonic -To add a private key to the `gnokey` keystore [using an existing mnemonic](#generating-a-bip39-mnemonic), -we can run the following command with the -`--recover` flag: - -```bash -gnokey add --recover MyKey -``` - -Of course, you can replace `MyKey` with whatever name you want for your key. - -By following the prompts to encrypt the key on disk, and providing a BIP39 -mnemonic, we can successfully add the key to the keystore. - -![gnokey add mnemonic](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-add-mnemonic.gif) - -## Deleting a private key -To delete a private key from the `gnokey` keystore, we need to know the name or -address of the key to remove. -After we have this information, we can run the following command: - -```bash -gnokey delete MyKey -``` - -After entering the key decryption password, the key will be deleted from the keystore. - -:::caution Recovering a private key -In case you delete or lose access to your private key in the `gnokey` keystore, -you can recover it using the key's mnemonic, or by importing it if it was exported -at a previous point in time. -::: - - -## Rotating the password of a private key to a new password -To rotate the password of a private key from the `gnokey` keystore to a new password, we need to know the name or -address of the key to remove. -After we have this information, we can run the following command: - -```bash -gnokey rotate MyKey -``` - -After entering the current key decryption password and the new password, the password of the key will be updated in the keystore. - -## Exporting a private key -Private keys stored in the `gnokey` keystore can be exported to a desired place -on the user's filesystem. - -Keys are exported in their original armor, encrypted or unencrypted. - -To export a key from the keystore, you can run: - -```bash -gnokey export -key MyKey -output-path ~/Work/gno-key.asc -``` - -Follow the prompts presented in the terminal. Namely, you will be asked to -decrypt the key in the keystore, and later to encrypt the armor file on disk. -It is worth noting that you can also export unencrypted key armor, using the `--unsafe` flag. - -![gnokey export](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-export.gif) - -## Importing a private key -If you have an exported private key file, you can import it into `gnokey` fairly -easily. - -For example, if the key is exported at `~/Work/gno-key.asc`, you can run the -following command: - -```bash -gnokey import -armor-path ~/Work/gno-key.asc -name ImportedKey -``` - -You will be asked to decrypt the encrypted private key armor on disk -(if it is encrypted, if not, use the `--unsafe` flag), and then to provide an -encryption password for storing the key in the keystore. - -After executing the previous command, the `gnokey` keystore will have imported -`ImportedKey`. - -![gnokey import](../../../assets/getting-started/local-setup/creating-a-key-pair/gnokey-import.gif) - -## Conclusion - -That's it! 🎉 - -In this tutorial, you've learned to use `gnokey` for managing Gno keypairs. - diff --git a/docs/gno-tooling/cli/gnoland.md b/docs/gno-tooling/cli/gnoland.md deleted file mode 100644 index 037a1f19d03..00000000000 --- a/docs/gno-tooling/cli/gnoland.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -id: gno-tooling-gnoland ---- - -# gnoland - -`gnoland` is the gno.land blockchain client binary, which is capable of managing -node working files, as well as starting the blockchain client itself. - -### gnoland start [flags] - -Starts the Gnoland blockchain node, with accompanying setup. - -#### FLAGS - -| Name | Type | Description | -|----------------------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `chainid` | String | The ID of the chain. (default: `dev`) | -| `data-dir` | String | The path to the node's data directory. This is an important folder. The chain may fail to start if this folder is contaminated. (default: `gnoland-data`) | -| `flag-config-path` | String | The flag config file (optional). | -| `genesis` | String | The path to the `genesis.json` file. (default: `genesis.json`) | -| `genesis-balance-file` | String | Initial distribution file. (default: `~/gno/gno.land/genesis/genesis_balances.txt`) | -| `genesis-max-vm-cycles` | Int | Sets maximum allowed vm cycles per operation. Zero means no limit. When increasing this option, the `block-max-gas` must also be increased to utilize the max cycles. (default: `100000000`) | -| `genesis-remote` | String | A replacement for `$$REMOTES%%` in genesis. (default: `localhost:26657`) | -| `genesis-txs-file` | String | Initial txs to replay. (default: ~/gno/gno.land/genesis/genesis_txs.jsonl) | -| `gnoroot-dir` | String | The root directory of the `gno` repository. (default: `~/gno`) | -| `lazy` | Boolean | Flag indication if lazy init is enabled. Generates the node secrets, configuration, and `genesis.json`. When set to `true`, you may start the chain without any initialization process, which comes in handy when developing. (default: `false`) | -| `log-format` | String | The log format for the gnoland node. (default: `console`) | -| `log-level` | String | The log level for the gnoland node. (default: `debug`) | -| `skip-failing-genesis-txs` | Boolean | Doesn’t panic when replaying invalid genesis txs. When starting a production-level chain, it is recommended to set this value to `true` to monitor and analyze failing transactions. (default: `false`) | - -### gnoland secrets \ [flags] [\…] - -The gno secrets manipulation suite for managing the validator key, p2p key and -validator state. - -#### SUBCOMMANDS - -| Name | Description | -|----------|---------------------------------------------------------| -| `init` | Initializes required Gno secrets in a common directory. | -| `verify` | Verifies all Gno secrets in a common directory. | -| `get` | Shows all Gno secrets present in a common directory. | - -### gnoland secrets init [flags] [\] - -Initializes the validator private key, the node p2p key and the validator's last -sign state. If a key is provided, it initializes the specified key. - -Available keys: - -- `validator_key` : The private key of the validator, which is different from the private key of the wallet. -- `node_id` : A key used for communicating with other nodes. -- `validator_state` : The current state of the validator such as the last signed block. - -#### FLAGS - -| Name | Type | Description | -|------------|--------|-----------------------------------------------------------------| -| `data-dir` | String | The secrets output directory. (default: `gnoland-data/secrets`) | -| `force` | String | Overwrites existing secrets, if any. (default: `false`) | - -```bash -# force initialize all key -gnoland secrets init -force - -Validator private key saved at gnoland-data/secrets/priv_validator_key.json -Validator last sign state saved at gnoland-data/secrets/priv_validator_state.json -Node key saved at gnoland-data/secrets/node_key.json - - -# force initialize a specific key type (ex: NodeKey) -gnoland secrets init node_key -force -Node key saved at gnoland-data/secrets/node_key.json -``` - -### gnoland secrets verify [flags] [\] - -Verifies the validator private key, the node p2p key and the validator's last -sign state. If a key is provided, it verifies the specified key value. - -Available keys: [`validator_key`, `node_id`, `validator_state`] - -#### FLAGS - -| Name | Type | Description | -|------------|--------|-----------------------------------------------------------------| -| `data-dir` | String | The secrets output directory. (default: `gnoland-data/secrets`) | - -```bash -# verify all keys -gnoland secrets verify -Validator Private Key at gnoland-data/secrets/priv_validator_key.json is valid -Last Validator Sign state at gnoland-data/secrets/priv_validator_state.json is valid -Node P2P key at gnoland-data/secrets/node_key.json is valid - - -# verify a specific key type (ex: NodeKey) -gnoland secrets verify node_key -Node P2P key at gnoland-data/secrets/node_key.json is valid -``` - -### gnoland secrets get [flags] [\] - -Shows the validator private key, the node p2p key and the validator's last sign -state. If a key is provided, it shows the specified key value. - -Available keys: [`validator_key`, `node_key`, `validator_state`] - -#### FLAGS - -| Name | Type | Description | -|------------|--------|-----------------------------------------------------------------| -| `data-dir` | String | The secrets output directory. (default: `gnoland-data/secrets)` | - -```bash -gnoland secrets get - -{ - "validator_key": { - "address": "g14j4dlsh3jzgmhezzp9v8xp7wxs4mvyskuw5ljl", - "pub_key": "gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqaqle3fdduqul4slg6zllypq9r8gj4wlfucy6qfnzmjcgqv675kxjz8jvk" - }, - "validator_state": { - "height": 0, - "round": 0, - "step": 0 - }, - "node_id": { - "id": "g17h5t86vrztm6vuesx0xsyrg90wplj9mt9nsxng", - "p2p_address": "g17h5t86vrztm6vuesx0xsyrg90wplj9mt9nsxng@0.0.0.0:26656" - } -} - -# will return node id info -gnoland secrets get node_id - -# to get node id in cosmos -# gaiad tendermint show-node-id - -# will return validator address and pub key -gnoland secrets get validator_key -# to get validator address in cosmos -# gaiad tendermint show-address - -# to get validator pub key in cosmos -# gaiad tendermint show-validator -``` - -### gnoland config [subcommand] [flags] - -The gno config manipulation suite for editing base and module configurations. - -#### SUBCOMMANDS - -| Name | Description | -|--------|-----------------------------------------| -| `init` | Initializes the Gno node configuration. | -| `set` | Edits the Gno node configuration. | -| `get` | Shows the Gno node configuration. | - -### gnoland config init [flags] - -Initializes the Gno node configuration locally with default values, which -includes the base and module configurations. - -#### FLAGS - -| Name | Type | Description | -|---------------|---------|------------------------------------------------------------------------------| -| `config-path` | String | The path for the `config.toml`. (default: `gnoland-data/config/config.toml`) | -| `force` | Boolean | Overwrites existing config.toml, if any. (default: `false`) | - -```bash -# initialize the configuration file -gnoland config init - -Default configuration initialized at gnoland-data/config/config.toml -``` - -### gnoland config set \ \ - -Edits the Gno node configuration at the given path by setting the option -specified at `\` to the given `\`. - -#### FLAGS - -| Name | Type | Description | -|---------------|--------|------------------------------------------------------------------------------| -| `config-path` | String | The path for the `config.toml`. (default: `gnoland-data/config/config.toml`) | - -:::info -The `config set` command replaces the complexity of manually editing the `config.toml` file. -::: - -### gnoland config get \ - -Shows the Gno node configuration at the given path by fetching the option -specified at `\`. - -#### FLAGS - -| Name | Type | Description | -|---------------|--------|---------------------------------------------------------------------------| -| `config-path` | String | the path for the config.toml (default: `gnoland-data/config/config.toml`) | - -```bash -# check the current monkier (the displayed validator name) -gnoland config get -r moniker -n3wbie-MacBook-Pro.local - -# set a new moniker -gnoland config set moniker hello -Updated configuration saved at gnoland-data/config/config.toml - - -# confirm the moniker change -gnoland config get -r moniker -hello -``` diff --git a/docs/gno-tooling/gno-tooling.md b/docs/gno-tooling/gno-tooling.md deleted file mode 100644 index 290d56820d0..00000000000 --- a/docs/gno-tooling/gno-tooling.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -id: gno-tooling ---- - -# Gno Tooling - -Welcome to the **Gno Tooling** section for Gno. This section outlines programs -& tools that are commonly used when developing applications with Gno. - -## Gno Command Syntax Guide - -### gno [subcommand] [flags] [arg...] - -#### Subcommand - -The gno command consists of various purpose-built subcommands. - -- `gno {mod}` : manage gno.mod -- `gno {mod} {download} : download modules to local cache - -#### Flags - -Options of the subcommand. - -- `gno mod download [-remote]` : remote for fetching gno modules - -#### Arg - -The actual value of the flag . - -- `gno mod download -remote {rpc.gno.land:26657}` diff --git a/docs/how-to-guides/connect-wallet-dapp.md b/docs/how-to-guides/connect-wallet-dapp.md deleted file mode 100644 index 294323b5560..00000000000 --- a/docs/how-to-guides/connect-wallet-dapp.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -id: connect-wallet-dapp ---- - -# How to connect a wallet to a dApp - -As a dapp developer, you must integrate a web3 wallet with your application to enable users to interact with your -application. Upon integration, you may retrieve account information of the connected user or request to sign & send -transactions from the user's account. - -:::warning Wallets on gno.land - -Here is a list of available wallets for Gnoland. -Note that none of these wallets are official or exclusive, so please -use them at your own diligence: - -- [Adena Wallet](https://adena.app/) - -::: - -## Adena Wallet - -[Adena](https://adena.app/) is a web extension wallet that supports the Gnoland blockchain. Below is the basic Adena -APIs that you can use for your application. For more detailed information, check out -Adena's [developer's docs](https://docs.adena.app/) to integrate Adena to your application. - -### Adena Connect For React App - -Check if Adena wallet exists. - -```javascript -// checks the existence of the adena object in window - -const existsWallet = () => { - if (window.adena) { - return true; - } - return false; -}; - -``` - -Register your website as a trusted domain. - -```javascript -// calls the AddEstablish of the adena object - -const addEstablish = (siteName) => { - return window?.adena?.AddEstablish(siteName); -}; - -``` - -Retrieve information about the connected account. - -```javascript -// calls the GetAccount function of the adena object - -const getAccount = () => { - return window.adena?.GetAccount(); -}; - -``` - -Request approval of a transaction that transfers tokens. - -```javascript -// Execute the DoContract function of the adena object to request transaction. - -const sendToken = (fromAddress, toAddress, sendAmount) => { - const message = { - type: "/bank.MsgSend", - value: { - from_address: fromAddress, - to_address: toAddress, - amount: sendAmount - } - }; - - return window.adena?.DoContract({ - messages: [message], - gasFee: 1, - gasWanted: 3000000 - }); -}; - -``` - -Request approval of a transaction that calls a function from a realm. - -```javascript -// Execute the DoContract function of the adena object to request transaction. - -const doContractPackageFunction = (caller, func, pkgPath, argument) => { - - // Setup Transaction Message - const message = { - type: "/vm.m_call", - value: { - caller, - func, - send: "", - pkg_path: pkgPath, - args: argument.split(',') - } - }; - - // Request Transaction - return window.adena?.DoContract({ - messages: [message], - gasFee: 1, - gasWanted: 3000000 - }); -}; -``` diff --git a/docs/how-to-guides/connecting-from-go.md b/docs/how-to-guides/connecting-from-go.md deleted file mode 100644 index 1c0478234fc..00000000000 --- a/docs/how-to-guides/connecting-from-go.md +++ /dev/null @@ -1,294 +0,0 @@ ---- -id: connect-from-go ---- - -# How to connect a Go app to gno.land - -This guide will show you how to connect to a gno.land network from your Go application, -using the [gnoclient](../reference/gnoclient/gnoclient.md) package. - -For this guide, we will build a small Go app that will: - -- Get account information from the chain -- Broadcast a state-changing transaction -- Read on-chain state - -## Prerequisites -- A local gno.land keypair generated using -[gnokey](../gno-tooling/cli/gnokey/working-with-key-pairs.md) - -## Setup - -To get started, create a new Go project. In a clean directory, run the following: -```bash -go mod init example -``` - -After this, create a new `main.go` file: - -```bash -touch main.go -``` - -Set up your main file with the code below: - -```go -package main - -func main() {} -``` - -Finally, add the `gnoclient` package by running the following command: - -```bash -go get github.com/gnolang/gno/gno.land/pkg/gnoclient -``` - -## Main components - -The `gnoclient` package exposes a `Client` struct containing a `Signer` and -`RPCClient` connector. `Client` exposes all available functionality for talking -to a gno.land chain. - -```go -type Client struct { - Signer Signer // Signer for transaction authentication - RPCClient rpcclient.Client // gnolang/gno/tm2/pkg/bft/rpc/client -} -``` - -### Signer - -The `Signer` provides functionality to sign transactions with a gno.land keypair. -The keypair can be accessed from a local keybase, or it can be generated -in-memory from a BIP39 mnemonic. - -:::info -The keybase directory path is set with the `gnokey --home` flag. -::: - -### RPCClient - -The `RPCCLient` provides connectivity to a gno.land network via HTTP or WebSockets. - - -## Initialize the Signer - -For this example, we will initialize the `Signer` from a local keybase: - -```go -package main - -import ( - "github.com/gnolang/gno/gno.land/pkg/gnoclient" - "github.com/gnolang/gno/tm2/pkg/crypto/keys" -) - -func main() { - // Initialize keybase from a directory - keybase, _ := keys.NewKeyBaseFromDir("path/to/keybase/dir") - - // Create signer - signer := gnoclient.SignerFromKeybase{ - Keybase: keybase, - Account: "", // Name of your keypair in keybase - Password: "", // Password to decrypt your keypair - ChainID: "", // id of gno.land chain - } -} -``` - -A few things to note: -- You can view keys in your local keybase by running `gnokey list`. -- You can get the password from a user input using the IO package. -- `Signer` can also be initialized in-memory from a BIP39 mnemonic, using the -[`SignerFromBip39`](https://gnolang.github.io/gno/github.com/gnolang/gno@v0.0.0/gno.land/pkg/gnoclient.html#SignerFromBip39) -function. - -## Initialize the RPC connection & Client - -You can initialize the RPC Client used to connect to the gno.land network with -the following line: -```go -rpc, err := rpcclient.NewHTTPClient("") -if err != nil { - panic(err) -} -``` - -A list of gno.land network endpoints & chain IDs can be found in the -[Gno RPC endpoints](../reference/network-config.md) page. - -With this, we can initialize the `gnoclient.Client` struct: - -```go -package main - -import ( - "github.com/gnolang/gno/gno.land/pkg/gnoclient" - "github.com/gnolang/gno/tm2/pkg/crypto/keys" - rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" -) - -func main() { - // Initialize keybase from a directory - keybase, _ := keys.NewKeyBaseFromDir("path/to/keybase/dir") - - // Create signer - signer := gnoclient.SignerFromKeybase{ - Keybase: keybase, - Account: "", // Name of your keypair in keybase - Password: "", // Password to decrypt your keypair - ChainID: "", // id of gno.land chain - } - - // Initialize the RPC client - rpc, err := rpcclient.NewHTTPClient("") - if err != nil { - panic(err) - } - - // Initialize the gnoclient - client := gnoclient.Client{ - Signer: signer, - RPCClient: rpc, - } -} -``` - -We can now communicate with the gno.land chain. Let's explore some of the functionality -`gnoclient` provides. - -## Query account info from a chain - -To send transactions to the chain, we need to know the account number (ID) and -sequence (nonce). We can get this information by querying the chain with the -`QueryAccount` function: - -```go -import ( - ... - "github.com/gnolang/gno/tm2/pkg/crypto" -) -``` - -```go -// Convert Gno address string to `crypto.Address` -addr, err := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // your Gno address -if err != nil { - panic(err) -} - -accountRes, _, err := client.QueryAccount(addr) -if err != nil { - panic(err) -} -``` - -An example result would be as follows: - -```go -fmt.Println(accountRes) -// Output: -// Account: -// Address: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -// Pubkey: -// Coins: 9999862000000ugnot -// AccountNumber: 0 -// Sequence: 0 -``` - -We are now ready to send a transaction to the chain. - -## Sending a transaction - -A gno.land transaction consists of two main parts: -- A set of base transaction fields, such as a gas price, gas limit, account & -sequence number, -- An array of messages to be executed on the chain. - -To construct the base set of transaction fields, we can use the `BaseTxCfg` type: -```go -txCfg := gnoclient.BaseTxCfg{ - GasFee: "1000000ugnot", // gas price - GasWanted: 1000000, // gas limit - AccountNumber: accountRes.GetAccountNumber(), // account ID - SequenceNumber: accountRes.GetSequence(), // account nonce - Memo: "This is a cool how-to guide!", // transaction memo -} -``` - -For calling an exported (public) function in a Gno realm, we can use the `MsgCall` -message type. We will use the wrapped ugnot realm for this example, wrapping -`1000000ugnot` (1 $GNOT) for demonstration purposes. - -```go -import ( - ... - "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - "github.com/gnolang/gno/tm2/pkg/std" -) -``` - -```go -msg := vm.MsgCall{ - Caller: addr, // address of the caller (signer) - PkgPath: "gno.land/r/demo/wugnot", // wrapped ugnot realm path - Func: "Deposit", // function to call - Args: nil, // arguments in string format - Send: std.Coins{{Denom: ugnot.Denom, Amount: int64(1000000)}}, // coins to send along with transaction -} -``` - -Finally, to actually call the function, we can use `Call`: - -```go -res, err := client.Call(txCfg, msg) -if err != nil { - panic(err) -} -``` - -Before running your code, make sure your keypair has enough funds to send the -transaction. - -If everything went well, you've just sent a state-changing transaction to a -gno.land chain! - - -## Reading on-chain state - -To read on-chain state, you can use the `QEval()` function. This functionality -allows you to evaluate a query expression on a realm, without having to spend gas. - -Let's fetch the balance of wrapped ugnot for our address: -```go -// Evaluate expression -qevalRes, _, err := client.QEval("gno.land/r/demo/wugnot", "BalanceOf(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")") -if err != nil { - panic(err) -} -``` - -Printing out the result should output: -```go -fmt.Println(qevalRes) -// Output: -// (1000000 uint64) -``` - -To see all functionality the `gnoclient` package provides, see the gnoclient -[reference page](../reference/gnoclient/gnoclient.md). - -## Conclusion - -Congratulations 🎉 - -You've just built a small demo app in Go that connects to a gno.land chain -to query account info, send a transaction, and read on-chain state. - -Check out the full example app code [here](https://github.com/leohhhn/connect-gno/blob/master/main.go). - -To see a real-world example CLI tool use `gnoclient`, -check out [gnoblog-cli](https://github.com/gnolang/blog/tree/main/cmd/gnoblog-cli). diff --git a/docs/how-to-guides/creating-grc20.md b/docs/how-to-guides/creating-grc20.md deleted file mode 100644 index 13f22fcc6a2..00000000000 --- a/docs/how-to-guides/creating-grc20.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -id: creating-grc20 ---- - -# How to Create a GRC20 Token -## Overview - -This guide shows you how to write a simple **GRC20** -a [Realm](../concepts/realms.md), in [Gno](../concepts/gno-language.md). For actually deploying the Realm, please see the -[deployment](deploy.md) guide. - -Our **GRC20** Realm will have the following functionality: - -- Minting a configurable amount of token. -- Keeping track of total token supply. -- Fetching the balance of an account. - -## 1. Importing token package -For this realm, we import the `grc20` package, as this includes -the main functionality of our token realm. The package can be found at the -`gno.land/p/demo/grc/grc20` path. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-1.gno go) -```go -package mytoken - -import ( - "std" - "strings" - - "gno.land/p/demo/grc/grc20" - "gno.land/p/demo/ufmt" -) - -var ( - banker *grc20.Banker - mytoken grc20.Token - admin std.Address -) - -// init is called once at time of deployment -func init() { - // Set deployer of Realm to admin - admin = std.PrevRealm().Addr() - - // Set token name, symbol and number of decimals - banker = grc20.NewBanker("My Token", "TKN", 4) - - // Mint 1 million tokens to admin - banker.Mint(admin, 1_000_000*10_000) // 1M - - // Get the GRC20 compatible safe object - mytoken = banker.Token() -} -``` - -The code snippet above does the following: -- Defines a new token variable, `banker`, and assigns it to a -pointer to the GRC20 banker type, `*grc20.Banker`, -- Defines and sets the value of `admin` with a type of `std.Address` to contain -the address of the deployer -- Initializes `mytoken` as a GRC20-compatible token, and sets its name, symbol, - and decimal values, -- Mint 1 million units of `My Token` and assign them to the admin's address. - -## 2. Adding token functionality - -In order to call exported functions from the `grc20` package, we also need to -expose them in the realm. Let's go through all functions in the GRC20 package, -one by one: - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*TotalSupply/ /^}/) -```go -// TotalSupply returns the total supply of mytoken -func TotalSupply() uint64 { - return mytoken.TotalSupply() -} -``` -Calling the `TotalSupply` method would return the total number of tokens minted. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*Decimals/ /^}/) -```go -// Decimals returns the number of decimals of mytoken -func Decimals() uint { - return mytoken.GetDecimals() -} -``` -Calling the `Decimals` method would return number of decimals of the token. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*BalanceOf/ /^}/) -```go -// BalanceOf returns the balance mytoken for `account` -func BalanceOf(account std.Address) uint64 { - return mytoken.BalanceOf(account) -} -``` - -Calling the `BalanceOf` method would return the total balance of an account. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*Allowance/ /^}/) -```go -// Allowance returns the allowance of spender on owner's balance -func Allowance(owner, spender std.Address) uint64 { - return mytoken.Allowance(owner, spender) -} -``` -Calling the `Allowance` method will return the amount `spender` is allowed to -spend from `owner`'s balance. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*Transfer/ /^}/) -```go -// Transfer transfers amount from caller to recipient -func Transfer(recipient std.Address, amount uint64) { - checkErr(mytoken.Transfer(recipient, amount)) -} -``` -Calling the `Transfer` method transfers amount of token from the calling account -to the recipient account. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*Approve/ /^}/) -```go -// Approve approves amount of caller's tokens to be spent by spender -func Approve(spender std.Address, amount uint64) { - checkErr(mytoken.Approve(spender, amount)) -} -``` -Calling the `Approve` method approves `spender` to spend `amount` from the caller's -balance of tokens. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*TransferFrom/ /^}/) -```go -// TransferFrom transfers `amount` of tokens from `from` to `to` -func TransferFrom(from, to std.Address, amount uint64) { - checkErr(mytoken.TransferFrom(from, to, amount)) -} -``` -Calling the `TransferFrom` method moves `amount` of tokens from `sender` to -`recipient` using the allowance mechanism. `amount` is then deducted from the -caller’s allowance. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*Mint/ /^}/) -```go -// Mint mints amount of tokens to address. Callable only by admin of token -func Mint(address std.Address, amount uint64) { - assertIsAdmin(std.PrevRealm().Addr()) - checkErr(banker.Mint(address, amount)) -} -``` -Calling the `Mint` method creates `amount` of tokens and assigns them to `address`, -increasing the total supply. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*Burn/ /^}/) -```go -// Burn burns amount of tokens from address. Callable only by admin of token -func Burn(address std.Address, amount uint64) { - assertIsAdmin(std.PrevRealm().Addr()) - checkErr(banker.Burn(address, amount)) -} -``` -Calling the `Mint` method burns `amount` of tokens from the balance of `address`, -decreasing the total supply. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*assertIsAdmin/ /^}/) -```go - assertIsAdmin(std.PrevRealm().Addr()) - checkErr(banker.Mint(address, amount)) -} -``` -Calling the `assertIsAdmin` method checks if `address` is equal to the -package-level `admin` variable. - -[embedmd]:# (../assets/how-to-guides/creating-grc20/mytoken-2.gno go /.*Render/ /^}/) -```go -// Render renders the state of the realm -func Render(path string) string { - parts := strings.Split(path, "/") - c := len(parts) - - switch { - case path == "": - // Default GRC20 render - return mytoken.RenderHome() - case c == 2 && parts[0] == "balance": - // Render balance of specific address - owner := std.Address(parts[1]) - balance, _ := mytoken.BalanceOf(owner) - return ufmt.Sprintf("%d\n", balance) - default: - return "404\n" - } -} -``` -Calling the `Render` method returns a general render of the GRC20 realm, or -if given a specific address, the user's `balance` as a formatted string. - -You can view the full code on [this Playground link](https://play.gno.land/p/RB_yIz9bAoB). -If you want to deploy it, do so on the [Portal Loop](../concepts/portal-loop.md). - -## Conclusion -That's it 🎉 - -You have successfully built a simple GRC20 Realm that is ready to be deployed on the Gno chain and called by users. -In the upcoming guides, we will see how we can develop more complex Realm logic and have them interact with outside tools like a wallet application. diff --git a/docs/how-to-guides/deploy.md b/docs/how-to-guides/deploy.md deleted file mode 100644 index 1e27ccd0cad..00000000000 --- a/docs/how-to-guides/deploy.md +++ /dev/null @@ -1,140 +0,0 @@ ---- -id: deploy ---- - -# How to deploy a Realm / Package - -## Overview - -This guide shows you how to deploy any realm or package to the Gno chain, -including how to: -- deploy Gno code in your browser via the Playground, -- deploy Gno code from your local machine using `gnokey`'s `maketx addpkg` API. - -## Deployment via the Playground - -Deployment via the Playground is recommended for smaller realms and packages. -For larger projects, it is recommended to write, test, and deploy your code from -a more appropriate environment, such as a local or online IDE. - -For this, check out the [**Deployment from a local environment**](#deployment-from-a-local-environment) section. - -### Prerequisites - -- **A gno.land-compatible wallet, such as [Adena](https://adena.app)** - -### Using Gno Playground - -To demonstrate deployment using the Playground, we'll use the **Counter** realm. -You can access the sample code via -[this Playground link](https://play.gno.land/p/iUWTha99D1J). - -Review the code and when you're ready, click on "**Deploy**". -Ensure your wallet is connected to proceed. If it isn't, a prompt will appear for connection: - -![DeployConnect](../assets/how-to-guides/deploy/deploy_connect.png) - -After connecting your wallet to the Playground, you will be prompted with a -new toolbox: - -![DeployDefault](../assets/how-to-guides/deploy/deploy_default.png) - -Here, you can choose the deployment path of your realm or package, as well as the network -to deploy to. The Playground also allows for deployment to a local node -if you are running one. - -:::info -Here are a few things to keep in mind when deploying packages and realms: -- The `name` field in the path should match your package name, in this case `counter`. -- Packages are deployed under `p/`, while realms are deployed under `r/`. - -An example path for the Counter realm could be the following: -```go -gno.land/r//counter -``` -::: - -After choosing a path and network, click on **Deploy**. A pop-up window -from your connected wallet will prompt you to sign and approve the deployment transaction. - -![DeployDefault](../assets/how-to-guides/deploy/deploy_success.png) - -If all went well, you will have successfully deployed your the Counter package. -Congratulations 🎉 - -## Deployment from a local environment - -### Prerequisites - -- **Have `gnokey` installed** -- **Have access to a `gnoland` node (local or remote)** -- **Have generated a keypair with `gnokey` & funded it with `gnot`** -- **Have a Realm or Package ready to deploy** - -### Deploying - -To illustrate deployment, we will use a realm. Consider the following folder -structure on a local file system: - -``` -counter-app/ -├─ r/ -│ ├─ counter/ -│ │ ├─ counter.gno -``` - -We would like to deploy the realm found in `counter.gno`. To do this, open a -terminal at `counter-app/` and use the following `gnokey` command: - -```bash -gnokey maketx addpkg \ ---pkgpath "gno.land/r/demo/counter" \ ---pkgdir "./r/counter" \ ---gas-fee 10000000ugnot \ ---gas-wanted 800000 \ ---broadcast \ ---chainid dev \ ---remote localhost:26657 \ -MyKey -``` - -Let's analyze all of the flags in detail: -- `--pkgpath` - path where the package/realm will be placed on-chain -- `--pkgdir` - local path where the package/realm is located -- `--gas-wanted` - the upper limit for units of gas for the execution of the -transaction - similar to Solidity's gas limit -- `--gas-fee` - similar to Solidity's gas-price -- `--broadcast` - broadcast the transaction on-chain -- `--chain-id` - id of the chain to connect to - local or remote -- `--remote` - `gnoland` node endpoint - local or remote -- `MyKey` - the keypair to use for the transaction - -:::info -As of October 2023, `--gas-fee` is fixed to 1gnot (10000000ugnot), with plans -to change it down the line. -::: - -Next, confirm the transaction with your keypair passphrase. If deployment was -successful, you will be presented with a message similar to the following: - -``` -OK! -GAS WANTED: 800000 -GAS USED: 775097 -``` -Depending on the size of the package/realm, you might need to increase amount -given in the `--gas-wanted` flag to cover the deployment cost. - -:::info -Regardless of whether you're deploying a realm or a package, you will be using -`gnokey`'s `maketx addpkg` - the usage of `maketx addpkg` in both cases is identical. -To read more about the `maketx addpkg` -subcommand, view the `gnokey` [reference](../gno-tooling/cli/gnokey/state-changing-calls.md#addpackage). -::: - - -## Conclusion - -That's it 🎉 - -You have now successfully deployed a realm/package to a gno.land chain. diff --git a/docs/how-to-guides/how-to-guides.md b/docs/how-to-guides/how-to-guides.md deleted file mode 100644 index 5c3425f2c54..00000000000 --- a/docs/how-to-guides/how-to-guides.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -id: how-to-guides ---- - -# How-to Guides - -Welcome to the **How-to Guides** section for Gno. This section outlines how to -complete specific tasks related to Gno, such as writing a realm & package, deploying -code to the chain, creating a GRC20 or GRC721 token, etc. diff --git a/docs/how-to-guides/porting-solidity-to-gno.md b/docs/how-to-guides/porting-solidity-to-gno.md deleted file mode 100644 index 85c426c4c83..00000000000 --- a/docs/how-to-guides/porting-solidity-to-gno.md +++ /dev/null @@ -1,680 +0,0 @@ ---- -id: port-solidity-to-gno ---- - -# Port a Solidity Contract to a Gno Realm - - -## Overview - -This guide shows you how to port a Solidity contract `Simple Auction` to a Gno Realm `auction.gno` with test cases (Test Driven Development (TDD) approach). - -You can check the Solidity contract in this [link](https://docs.soliditylang.org/en/latest/solidity-by-example.html#simple-open-auction), and here's the code for porting. - -```solidity -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.4; -contract SimpleAuction { - // Parameters of the auction. Times are either - // absolute unix timestamps (seconds since 1970-01-01) - // or time periods in seconds. - address payable public beneficiary; - uint public auctionEndTime; - - // Current state of the auction. - address public highestBidder; - uint public highestBid; - - // Allowed withdrawals of previous bids - mapping(address => uint) pendingReturns; - - // Set to true at the end, disallows any change. - // By default initialized to `false`. - bool ended; - - // Events that will be emitted on changes. - event HighestBidIncreased(address bidder, uint amount); - event AuctionEnded(address winner, uint amount); - - // Errors that describe failures. - - // The triple-slash comments are so-called natspec - // comments. They will be shown when the user - // is asked to confirm a transaction or - // when an error is displayed. - - /// The auction has already ended. - error AuctionAlreadyEnded(); - /// There is already a higher or equal bid. - error BidNotHighEnough(uint highestBid); - /// The auction has not ended yet. - error AuctionNotYetEnded(); - /// The function auctionEnd has already been called. - error AuctionEndAlreadyCalled(); - - /// Create a simple auction with `biddingTime` - /// seconds bidding time on behalf of the - /// beneficiary address `beneficiaryAddress`. - constructor( - uint biddingTime, - address payable beneficiaryAddress - ) { - beneficiary = beneficiaryAddress; - auctionEndTime = block.timestamp + biddingTime; - } - - /// Bid on the auction with the value sent - /// together with this transaction. - /// The value will only be refunded if the - /// auction is not won. - function bid() external payable { - // No arguments are necessary, all - // information is already part of - // the transaction. The keyword payable - // is required for the function to - // be able to receive Ether. - - // Revert the call if the bidding - // period is over. - if (block.timestamp > auctionEndTime) - revert AuctionAlreadyEnded(); - - // If the bid is not higher, send the - // money back (the revert statement - // will revert all changes in this - // function execution including - // it having received the money). - if (msg.value <= highestBid) - revert BidNotHighEnough(highestBid); - - if (highestBid != 0) { - // Sending back the money by simply using - // highestBidder.send(highestBid) is a security risk - // because it could execute an untrusted contract. - // It is always safer to let the recipients - // withdraw their money themselves. - pendingReturns[highestBidder] += highestBid; - } - highestBidder = msg.sender; - highestBid = msg.value; - emit HighestBidIncreased(msg.sender, msg.value); - } - - /// Withdraw a bid that was overbid. - function withdraw() external returns (bool) { - uint amount = pendingReturns[msg.sender]; - if (amount > 0) { - // It is important to set this to zero because the recipient - // can call this function again as part of the receiving call - // before `send` returns. - pendingReturns[msg.sender] = 0; - - // msg.sender is not of type `address payable` and must be - // explicitly converted using `payable(msg.sender)` in order - // use the member function `send()`. - if (!payable(msg.sender).send(amount)) { - // No need to call throw here, just reset the amount owing - pendingReturns[msg.sender] = amount; - return false; - } - } - return true; - } - - /// End the auction and send the highest bid - /// to the beneficiary. - function auctionEnd() external { - // It is a good guideline to structure functions that interact - // with other contracts (i.e. they call functions or send Ether) - // into three phases: - // 1. checking conditions - // 2. performing actions (potentially changing conditions) - // 3. interacting with other contracts - // If these phases are mixed up, the other contract could call - // back into the current contract and modify the state or cause - // effects (ether payout) to be performed multiple times. - // If functions called internally include interaction with external - // contracts, they also have to be considered interaction with - // external contracts. - - // 1. Conditions - if (block.timestamp < auctionEndTime) - revert AuctionNotYetEnded(); - if (ended) - revert AuctionEndAlreadyCalled(); - - // 2. Effects - ended = true; - emit AuctionEnded(highestBidder, highestBid); - - // 3. Interaction - beneficiary.transfer(highestBid); - } -} -``` - -These are the basic concepts of the Simple Auction contract: - -* Everyone can send their bids during a bidding period. -* The bids already include sending money / Ether in order to bind the bidders to their bids. -* If the highest bid is raised, the previous highest bidder gets their money back. -* After the end of the bidding period, the contract has to be called manually for the beneficiary to receive their money - contracts cannot activate themselves. - -The contract consists of: - -* A variable declaration -* Initialization by a constructor -* Three functions - -Let's dive into the details of the role of each function, and learn how to port each function into Gno with test cases. - -When writing a test case, the following conditions are often used to determine whether the function has been properly executed: - -* Value matching -* Error status -* Panic status - -Below is a test case helper that will help implement each condition. - -### Gno - Testcase Helper - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-1.gno go) -```go -func shouldEqual(t *testing.T, got interface{}, expected interface{}) { - t.Helper() - - if got != expected { - t.Errorf("expected %v(%T), got %v(%T)", expected, expected, got, got) - } -} - -func shouldErr(t *testing.T, err error) { - t.Helper() - if err == nil { - t.Errorf("expected an error, but got nil.") - } -} - -func shouldNoErr(t *testing.T, err error) { - t.Helper() - if err != nil { - t.Errorf("expected no error, but got err: %s.", err.Error()) - } -} - -func shouldPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("should have panic") - } - }() - f() -} - -func shouldNoPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r != nil { - t.Errorf("should not have panic") - } - }() - f() -} -``` - -## Variable init - Solidity - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-2.sol solidity) -```solidity -// Parameters of the auction. Times are either -// absolute unix timestamps (seconds since 1970-01-01) -// or time periods in seconds. -address payable public beneficiary; -uint public auctionEndTime; - -// Current state of the auction. -address public highestBidder; -uint public highestBid; - -// Allowed withdrawals of previous bids -mapping(address => uint) pendingReturns; - -// Set to true at the end, disallows any change. -// By default initialized to `false`. -bool ended; - -// Events that will be emitted on changes. -event HighestBidIncreased(address bidder, uint amount); -event AuctionEnded(address winner, uint amount); - -// Errors that describe failures. - -// The triple-slash comments are so-called natspec -// comments. They will be shown when the user -// is asked to confirm a transaction or -// when an error is displayed. - -/// The auction has already ended. -error AuctionAlreadyEnded(); -/// There is already a higher or equal bid. -error BidNotHighEnough(uint highestBid); -/// The auction has not ended yet. -error AuctionNotYetEnded(); -/// The function auctionEnd has already been called. -error AuctionEndAlreadyCalled(); - -/// Create a simple auction with `biddingTime` -/// seconds bidding time on behalf of the -/// beneficiary address `beneficiaryAddress`. -constructor( - uint biddingTime, - address payable beneficiaryAddress -) { - beneficiary = beneficiaryAddress; - auctionEndTime = block.timestamp + biddingTime; -} -``` - -* `address payable public beneficiary;` : Address to receive the amount after the auction's ending. -* `uint public auctionEndTime;` : Auction ending time. -* `address public highestBidder;` : The highest bidder. -* `uint public highestBid;` : The highest bid. -* `mapping(address => uint) pendingReturns;` : Bidder's address and amount to be returned (in case of the highest bid changes). -* `bool ended;` : Whether the auction is closed. - -### Variable init - Gno - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-3.gno go) -```go -var ( - receiver = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - auctionEndBlock = std.GetHeight() + uint(300) // in blocks - highestBidder std.Address - highestBid = uint(0) - pendingReturns avl.Tree - ended = false -) -``` - -> **Note:** In Solidity, the Auction ending time is set by a time basis, but in the above case, it's set by a block basis. - -### - -## bid() - Solidity - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-4.sol solidity) -```solidity -function bid() external payable { - // No arguments are necessary, all - // information is already part of - // the transaction. The keyword payable - // is required for the function to - // be able to receive Ether. - - // Revert the call if the bidding - // period is over. - if (block.timestamp > auctionEndTime) - revert AuctionAlreadyEnded(); - - // If the bid is not higher, send the - // money back (the revert statement - // will revert all changes in this - // function execution including - // it having received the money). - if (msg.value <= highestBid) - revert BidNotHighEnough(highestBid); - - if (highestBid != 0) { - // Sending back the money by simply using - // highestBidder.send(highestBid) is a security risk - // because it could execute an untrusted contract. - // It is always safer to let the recipients - // withdraw their money themselves. - pendingReturns[highestBidder] += highestBid; - } - highestBidder = msg.sender; - highestBid = msg.value; - emit HighestBidIncreased(msg.sender, msg.value); -} -``` - -`bid()` function is for participating in an auction and includes: - -* Determining whether an auction is closed. -* Comparing a new bid with the current highest bid. -* Prepare data to return the bid amount to the existing highest bidder in case of the highest bid is increased. -* Update variables with the top bidder & top bid amount. - -### bid() - Gno - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-5.gno go) -```go -func Bid() { - if std.GetHeight() > auctionEndBlock { - panic("Exceeded auction end block") - } - - sentCoins := std.GetOrigSend() - if len(sentCoins) != 1 { - panic("Send only one type of coin") - } - - sentAmount := uint(sentCoins[0].Amount) - if sentAmount <= highestBid { - panic("Too few coins sent") - } - - // A new bid is higher than the current highest bid - if sentAmount > highestBid { - // If the highest bid is greater than 0, - if highestBid > 0 { - // Need to return the bid amount to the existing highest bidder - // Create an AVL tree and save - pendingReturns.Set(highestBidder.String(), highestBid) - } - - // Update the top bidder address - highestBidder = std.GetOrigCaller() - // Update the top bid amount - highestBid = sentAmount - } -} -``` - -### bid() - Gno Testcase - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-6.gno go) -```go -// Bid Function Test - Send Coin -func TestBidCoins(t *testing.T) { - // Sending two types of coins - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) - shouldPanic(t, Bid) - - // Sending lower amount than the current highest bid - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) - shouldPanic(t, Bid) - - // Sending more amount than the current highest bid (exceeded) - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldNoPanic(t, Bid) -} - -// Bid Function Test - Bid by two or more people -func TestBidCoins(t *testing.T) { - // bidder01 bidding with 1 coin - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldNoPanic(t, Bid) - shouldEqual(t, highestBid, 1) - shouldEqual(t, highestBidder, bidder01) - shouldEqual(t, pendingReturns.Size(), 0) - - // bidder02 bidding with 1 coin - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldPanic(t, Bid) - - // bidder02 bidding with 2 coins - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) - shouldNoPanic(t, Bid) - shouldEqual(t, highestBid, 2) - shouldEqual(t, highestBidder, bidder02) - shouldEqual(t, pendingReturns.Size(), 1) -} -``` - -### - -## withdraw() - Solidity - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-7.sol solidity) -```solidity -/// Withdraw a bid that was overbid. -function withdraw() external returns (bool) { - uint amount = pendingReturns[msg.sender]; - if (amount > 0) { - // It is important to set this to zero because the recipient - // can call this function again as part of the receiving call - // before `send` returns. - pendingReturns[msg.sender] = 0; - - // msg.sender is not of type `address payable` and must be - // explicitly converted using `payable(msg.sender)` in order - // use the member function `send()`. - if (!payable(msg.sender).send(amount)) { - // No need to call throw here, just reset the amount owing - pendingReturns[msg.sender] = amount; - return false; - } - } - return true; -} -``` - -`withdraw()` is to return the bid amount to the existing highest bidder in case of the highest bid changes and includes: - -* When called, determine if there's a bid amount to be returned to the address. -* (If there's an amount to be returned) Before returning, set the previously recorded amount to `0` and return the actual amount. - -### withdraw() - Gno - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-8.gno go) -```go -func Withdraw() { - // Query the return amount to non-highest bidders - amount, _ := pendingReturns.Get(std.GetOrigCaller().String()) - - if amount > 0 { - // If there's an amount, reset the amount first, - pendingReturns.Set(std.GetOrigCaller().String(), 0) - - // Return the exceeded amount - banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() - - banker.SendCoins(pkgAddr, std.GetOrigCaller(), std.Coins{{"ugnot", amount.(int64)}}) - } -} -``` - -### - -### withdraw() - Gno Testcase - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-9.gno go) -```go -// Withdraw Function Test -func TestWithdraw(t *testing.T) { - // If there's no participants for return - shouldEqual(t, pendingReturns.Size(), 0) - - // If there's participants for return (data generation - returnAddr := bidder01.String() - returnAmount := int64(3) - pendingReturns.Set(returnAddr, returnAmount) - shouldEqual(t, pendingReturns.Size(), 1) - shouldEqual(t, pendingReturns.Has(returnAddr), true) - - banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() - banker.SendCoins(pkgAddr, std.Address(returnAddr), std.Coins{{"ugnot", returnAmount}}) - shouldEqual(t, banker.GetCoins(std.Address(returnAddr)).String(), "3ugnot") -} -``` - -## auctionEnd() - Solidity - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-10.sol solidity) -```solidity -/// End the auction and send the highest bid -/// to the beneficiary. -function auctionEnd() external { - // It is a good guideline to structure functions that interact - // with other contracts (i.e. they call functions or send Ether) - // into three phases: - // 1. checking conditions - // 2. performing actions (potentially changing conditions) - // 3. interacting with other contracts - // If these phases are mixed up, the other contract could call - // back into the current contract and modify the state or cause - // effects (ether payout) to be performed multiple times. - // If functions called internally include interaction with external - // contracts, they also have to be considered interaction with - // external contracts. - - // 1. Conditions - if (block.timestamp < auctionEndTime) - revert AuctionNotYetEnded(); - if (ended) - revert AuctionEndAlreadyCalled(); - - // 2. Effects - ended = true; - emit AuctionEnded(highestBidder, highestBid); - - // 3. Interaction - beneficiary.transfer(highestBid); -} -``` - -`auctionEnd()` function is for ending the auction and includes: - -* Determines if the auction should end by comparing the end time. -* Determines if the auction has already ended or not. - * (If not ended) End the auction. - * (If not ended) Send the highest bid amount to the recipient. - -### auctionEnd() - Gno - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-11.gno go) -```go -func AuctionEnd() { - if std.GetHeight() < auctionEndBlock { - panic("Auction hasn't ended") - } - - if ended { - panic("Auction has ended") - - } - ended = true - - // Send the highest bid to the recipient - banker := std.GetBanker(std.BankerTypeRealmSend) - pkgAddr := std.GetOrigPkgAddr() - - banker.SendCoins(pkgAddr, receiver, std.Coins{{"ugnot", int64(highestBid)}}) -} -``` - -### auctionEnd() - Gno Testcase - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-12.gno go) -```go -// AuctionEnd() Function Test -func TestAuctionEnd(t *testing.T) { - // Auction is ongoing - shouldPanic(t, AuctionEnd) - - // Auction ends - highestBid = 3 - std.TestSkipHeights(500) - shouldNoPanic(t, AuctionEnd) - shouldEqual(t, ended, true) - - banker := std.GetBanker(std.BankerTypeRealmSend) - shouldEqual(t, banker.GetCoins(receiver).String(), "3ugnot") - - // Auction has already ended - shouldPanic(t, AuctionEnd) - shouldEqual(t, ended, true) -} -``` - -## Precautions for Running Test Cases - -* Each test function should be executed separately one by one, to return all passes without any errors. -* Same as Go, Gno doesn't support `setup()` & `teardown()` functions. So running two or more test functions simultaneously can result in tainted data. -* If you want to do the whole test at once, make it into a single function as below: - -[embedmd]:# (../assets/how-to-guides/porting-solidity-to-gno/porting-13.gno go) -```go -// The whole test -func TestFull(t *testing.T) { - bidder01 := testutils.TestAddress("bidder01") // g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw - bidder02 := testutils.TestAddress("bidder02") // g1vf5kger9wgcryh6lta047h6lta047h6lnhe2x2 - - // Variables test - { - shouldEqual(t, highestBidder, "") - shouldEqual(t, receiver, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - shouldEqual(t, auctionEndBlock, 423) - shouldEqual(t, highestBid, 0) - shouldEqual(t, pendingReturns.Size(), 0) - shouldEqual(t, ended, false) - } - - // Send two or more types of coins - { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}, {"test", 1}}, nil) - shouldPanic(t, Bid) - } - - // Send less than the highest bid - { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 0}}, nil) - shouldPanic(t, Bid) - } - - // Send more than the highest bid - { - std.TestSetOrigCaller(bidder01) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldNoPanic(t, Bid) - - shouldEqual(t, pendingReturns.Size(), 0) - shouldEqual(t, highestBid, 1) - shouldEqual(t, highestBidder, "g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw") - } - - // Other participants in the auction - { - - // Send less amount than the current highest bid (current: 1) - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 1}}, nil) - shouldPanic(t, Bid) - - // Send more amount than the current highest bid (exceeded) - std.TestSetOrigCaller(bidder02) - std.TestSetOrigSend(std.Coins{{"ugnot", 2}}, nil) - shouldNoPanic(t, Bid) - - shouldEqual(t, highestBid, 2) - shouldEqual(t, highestBidder, "g1vf5kger9wgcryh6lta047h6lta047h6lnhe2x2") - - shouldEqual(t, pendingReturns.Size(), 1) // Return to the existing bidder - shouldEqual(t, pendingReturns.Has("g1vf5kger9wgcrzh6lta047h6lta047h6lufftkw"), true) - } - - // Auction ends - { - std.TestSkipHeights(150) - shouldPanic(t, AuctionEnd) - shouldEqual(t, ended, false) - - std.TestSkipHeights(301) - shouldNoPanic(t, AuctionEnd) - shouldEqual(t, ended, true) - - banker := std.GetBanker(std.BankerTypeRealmSend) - shouldEqual(t, banker.GetCoins(receiver).String(), "2ugnot") - } -} -``` diff --git a/docs/how-to-guides/simple-contract.md b/docs/how-to-guides/simple-contract.md deleted file mode 100644 index c9aac45971d..00000000000 --- a/docs/how-to-guides/simple-contract.md +++ /dev/null @@ -1,176 +0,0 @@ ---- -id: simple-contract ---- - -# How to write a simple Gno Smart Contract (Realm) - -## Overview - -This guide shows you how to write a simple **Counter** Smart Contract, or rather a [realm](../concepts/realms.md), -in [Gno](../concepts/gno-language.md). - -For actually deploying the Realm, please see the [deployment](deploy.md) guide. - -Our **Counter** realm will have the following functionality: - -- Keeping track of the current count. -- Incrementing / decrementing the count. -- Fetching the current count value. - -## 1. Development environment -Currently, Gno apps can be developed locally or via the online editor, Gno -Playground. Below we detail how to set up and use both. - -### Local setup - -#### Prerequisites - -- **Text editor** - -:::info Editor support -The Gno language is based on Go, but it does not have all the bells and whistles in major text editors like Go. -Advanced language features like IntelliSense are still in the works. - -Currently, we officially have language support -for [ViM](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md#vim-support), -[Emacs](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md#emacs-support) -and [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=harry-hov.gno). -::: - -To get started with a local setup, simply create a new empty folder. - -```bash -mkdir counter-app -``` - -Gno realms can be typically written anywhere, under any structure, just like -regular Go code. However, Gno developers have adopted a standard of organizing -Gno logic under a specific directory hierarchy, which we -will explore in this section. - -Since we are building a simple **Counter** realm, inside our created `counter-app` -directory, we can create another directory named `r`, which stands for `realm`: - -```bash -cd counter-app -mkdir r -``` - -Alternatively, if we were writing a [Gno package](../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`: - -```bash -cd r -mkdir counter -``` - -After setting up our work directory structure, we should have something like this: - -```text -counter-app/ -├─ r/ -│ ├─ counter/ -│ │ ├─ // source code here -``` - -Now that the work directory structure is set up, we can go into the `counter` -sub-folder, and actually create the file to store our **Counter** realm: - -```bash -cd counter -touch counter.gno -``` - -:::info Gno file extension -All Gno source code has the file extension `.gno`. - -This file extension is required for existing gno tools and processes to work. -::: - -You're ready to write Gno code! Skip to ["Start writing code"](#2-start-writing-code) -to see how to start. - -### Using the Gno Playground - -For smaller apps and Gno code snippets, the Gno Playground can be used. It provides -a simple sandbox environment where developers can write, test, and deploy Gno code. - -Visiting the [Playground](https://play.gno.land) will greet you with a template file: - -![Default](../assets/how-to-guides/simple-contract/playground_welcome.png) - -Create a new file named `counter.gno`, and delete the default file. You are now -ready to write some Gno code! - -## 2. Start writing code - -After setting up your environment, we can start defining the logic of our counter -app. Inside `counter.gno`: - -[embedmd]:# (../assets/how-to-guides/simple-contract/counter.gno go) -```go -package counter - -import ( - "gno.land/p/demo/ufmt" -) - -var count int - -func Increment() { - count++ -} - -func Decrement() { - count-- -} - -func Render(_ string) string { - return ufmt.Sprintf("Count: %d", count) -} -``` - -There are a few things happening here, so let's dissect them: - -- We defined the logic of our Realm into a package called `counter`. -- The package-level `count` variable stores the active count for the Realm (it is stateful). -- `Increment` and `Decrement` are public Realm 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](../concepts/realms.md). - -You can view the code on [this Playground link](https://play.gno.land/p/ONBa9eUEPKJ). - -:::info A note on constructors -Gno Realms support a concept taken from other programming languages - _constructors_. - -For example, to initialize the `count` variable with custom logic, we can specify that -logic within an `init` method, that is run **only once**, upon Realm deployment: - -[embedmd]:# (../assets/how-to-guides/simple-contract/init.gno go) -```go -package counter - -var count int - -// ... - -func init() { - count = 2 * 10 // arbitrary value -} - -// ... -``` - -::: - -## Conclusion - -That's it 🎉 - -You have successfully built a simple **Counter** Realm that is ready to be deployed on the Gno chain and called by users. -In the upcoming guides, we will see how we can develop more complex Realm logic and have them interact -with outside tools like a wallet application. diff --git a/docs/how-to-guides/simple-library.md b/docs/how-to-guides/simple-library.md deleted file mode 100644 index 62ff2bd2c0f..00000000000 --- a/docs/how-to-guides/simple-library.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -id: simple-library ---- - -# How to write a simple Gno Library (Package) - -## Overview - -This guide shows you how to write a simple library (Package) in Gno, 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 concept page](../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. - -## Development environment -Currently, Gno packages can be developed locally or via the online editor, Gno -Playground. Below we detail how to set up and use both. - -### Local setup - -#### Prerequisites - -- **Text editor** - -:::info Editor support -The Gno language is based on Go, but it does not have all the bells and whistles in major text editors like Go. -Advanced language features like IntelliSense are still in the works. - -Currently, we officially have language support -for [ViM](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md#vim-support), -[Emacs](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md#emacs-support) -and [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=harry-hov.gno). -::: - -We discussed Gno folder structures more in detail in -the [simple Smart Contract guide](simple-contract.md#local-setup). -For now, we will just follow some rules outlined there. - -Create the main working directory for our Package: - -```bash -mkdir tapas-lib -``` - -Since we are building a simple tapas Package, inside our created `tapas-lib` directory, we can create another -directory named `p`, which stands for `package`: - -```bash -cd tapas-lib -mkdir p -``` - -Additionally, we will create another subdirectory that will house our Package code, named `tapas`: - -```bash -cd p -mkdir tapas -``` - -After setting up our work directory structure, we should have something like this: - -```text -tapas-lib/ -├─ p/ -│ ├─ tapas/ -│ │ ├─ // source code here -``` - -Now that the work directory structure is set up, we can go into the `tapas` sub-folder, and actually create -our tapas suggestion library logic: - -```bash -cd tapas -touch tapas.gno -``` - -You're ready to write Gno code! Skip to ["Start writing code"](#2-start-writing-code) -to see how to start. - -### Using Gno Playground - -When using the Gno Playground, writing, testing, deploying, and sharing Gno code -is simple. This makes it perfect for getting started with Gno. - -Visiting the [Playground](https://play.gno.land) will greet you with a template file: - -![Default](../assets/how-to-guides/simple-library/playground_welcome.png) - -Create a new file named `tapas.gno`, and delete the default file. You are now -ready to write some Gno code! - - -## 2. Start writing code - -After setting up your environment, we can start defining our library logic. -Inside `tapas.gno`: - -[embedmd]:# (../assets/how-to-guides/simple-library/tapas.gno go) -```go -package tapas - -import "std" - -// List of tapas suggestions -var listOfTapas = []string{ - "Patatas Bravas", - "Gambas al Ajillo", - "Croquetas", - "Tortilla Española", - "Pimientos de Padrón", - "Jamon Serrano", - "Boquerones en Vinagre", - "Calamares a la Romana", - "Pulpo a la Gallega", - "Tostada con Tomate", - "Mejillones en Escabeche", - "Chorizo a la Sidra", - "Cazón en Adobo", - "Banderillas", - "Espárragos a la Parrilla", - "Huevos Rellenos", - "Tuna Empanada", - "Sardinas a la Plancha", -} - -// GetTapaSuggestion randomly selects and returns a tapa suggestion -func GetTapaSuggestion(userInput string) string { - - // Create a random number depending on the block height. - // We get the block height using std.GetHeight(), which is from an imported Gno library, "std" - // Note: this value is not fully random and is easily guessable - randomNumber := int(std.GetHeight()) % len(listOfTapas) - - // Return the random suggestion - return listOfTapas[randomNumber] -} -``` - -There are a few things happening here, so let's dissect them: - -- We defined the logic of our library into a package called `tapas`. -- The package imports `std`, which -is the [Gno standard library](../concepts/stdlibs/stdlibs.md) -- We use the imported package inside of `GetTapaSuggestion` to generate a -random index value for a tapa - -You can view the code on [this Playground link](https://play.gno.land/p/3uwBqP66ekC). - -## Conclusion - -That's it 🎉 - -You have successfully built a simple tapas suggestion Package that is ready to be deployed on the Gno chain and imported -by other Packages and Realms. diff --git a/docs/how-to-guides/testing-gno.md b/docs/how-to-guides/testing-gno.md deleted file mode 100644 index e32a9435711..00000000000 --- a/docs/how-to-guides/testing-gno.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -id: testing-gno ---- - -# How to test Gno Code - -## Overview - -In this guide, we will explore the available tooling in testing out the Gno Realms and Packages we write. -We will go over different CLI tools available to developers, gno testing libraries as well as -testing techniques that involve data mocking. - -## Prerequisites - -- **`gno` set up. Reference the [Installation](../getting-started/local-setup/local-setup.md#3-installing-other-gno-tools) guide - for steps** - -## Example Realm - -For the purpose of this guide, we will be testing the simple *Counter* Realm created in -the [How to write a simple Gno Smart Contract (Realm)](simple-contract.md) guide. - -[embedmd]:# (../assets/how-to-guides/testing-gno/counter-1.gno go) -```go -// counter-app/r/counter/counter.gno - -package counter - -import ( - "gno.land/p/demo/ufmt" -) - -var count int - -func Increment() { - count++ -} - -func Decrement() { - count-- -} - -func Render(_ string) string { - return ufmt.Sprintf("Count: %d", count) -} -``` - -## 1. Writing the Gno test - -Gno tests are written in the same manner and format as regular Go tests, just in `_test.gno` files. - -We can place the Gno tests for the `Counter` Realm in the same directory as `counter.gno`: - -```text -counter-app/ -├─ r/ -│ ├─ counter/ -│ │ ├─ counter.gno -│ │ ├─ counter_test.gno <--- the test source code -``` - -```bash -cd counter -touch counter_test.gno -``` - -What should be tested in this _Counter_ Realm example? -Mainly, we want to verify that: - -- Increment increments the value. -- Decrement decrements the value. -- Render returns a valid formatted value. - -Let's write the required unit tests: - -[embedmd]:# (../assets/how-to-guides/testing-gno/counter-2.gno go) -```go -// counter-app/r/counter/counter_test.gno - -package counter - -import "testing" - -func TestCounter_Increment(t *testing.T) { - // Reset the value - count = 0 - - // Verify the initial value is 0 - if count != 0 { - t.Fatalf("initial value != 0") - } - - // Increment the value - Increment() - - // Verify the initial value is 1 - if count != 1 { - t.Fatalf("initial value != 1") - } -} - -func TestCounter_Decrement(t *testing.T) { - // Reset the value - count = 0 - - // Verify the initial value is 0 - if count != 0 { - t.Fatalf("initial value != 0") - } - - // Decrement the value - Decrement() - - // Verify the initial value is 1 - if count != -1 { - t.Fatalf("initial value != -1") - } -} - -func TestCounter_Render(t *testing.T) { - // Reset the value - count = 0 - - // Verify the Render output - if Render("") != "Count: 0" { - t.Fatalf("invalid Render value") - } -} -``` - -:::warning Testing package-level variables - -In practice, it is not advisable to test and validate package level variables like this, as their value is mutated -between test runs. For the sake of keeping this guide simple, we went ahead and reset the variable value for each test, -however, -you should employ more robust test strategies. - -::: - -## 2. Running the Gno test - -To run the prepared Gno tests, we can utilize the `gno test` CLI tool. - -Simply point it to the location containing our testing source code, and the tests will execute. -For example, we can run the following command from the `counter-app/r/counter` directory: - -```bash -gno test -v . -``` - -Let's look into the different parts of this command: - -- `-v` enables the verbose output. -- `-root-dir` specifies the root directory to our cloned `gno` GitHub repository -- `.` specifies the location containing our test files. Since we are already located in that directory, we specify - a `.`. - -Running the test command should produce a successful output: - -```bash -=== RUN TestCounter_Increment ---- PASS: TestCounter_Increment (0.00s) -=== RUN TestCounter_Decrement ---- PASS: TestCounter_Decrement (0.00s) -=== RUN TestCounter_Render ---- PASS: TestCounter_Render (0.00s) -ok ./. 1.00s -``` - -## Additional test support - -As we grow more familiar with Gno development, our Realm / Package logic can become more complex. As such, we need -more robust testing support in the form of mocking values ahead of time that would normally be only available on a -live (deployed) Realm / Package. - -Luckily, the Gno standard library provides ample support for functionality such as setting predefined values ahead of -time, such as the request caller address, or the calling package address. - -You can learn more about these methods, that are importable using the `std` import declaration, -in the [Standard Library](../concepts/stdlibs/stdlibs.md) reference section. - -## Conclusion - -That's it 🎉 - -You have successfully written and tested Gno code. Additionally, you have utilized the `gno test` tool, and understood -how it can be configured to make the developer experience smooth. diff --git a/docs/how-to-guides/write-simple-dapp.md b/docs/how-to-guides/write-simple-dapp.md deleted file mode 100644 index f844f8ab7c8..00000000000 --- a/docs/how-to-guides/write-simple-dapp.md +++ /dev/null @@ -1,308 +0,0 @@ ---- -id: write-simple-dapp ---- - -# How to write a simple dApp on gno.land - -## Overview - -This guide will show you how to write a complete dApp that combines both a package and a realm. -Our app will allow any user to create a poll, and subsequently vote -YAY or NAY for any poll that has not exceeded the voting deadline. - -## Defining dApp functionality - -Our dApp will consist of a Poll package, which will handle all things related to the Poll struct, -and a Poll Factory realm, which will handle the user-facing functionality and rendering. - -For simplicity, we will define the functionality in plain text, and leave comments explaining the code. - -### Poll Package - -- Defines a `Poll` struct -- Defines a `NewPoll` constructor -- Defines `Poll` field getters -- Defines a `Vote` function -- Defines a `HasVoted` check method -- Defines a `VoteCount` getter method - -[embedmd]:# (../assets/how-to-guides/write-simple-dapp/poll-1.gno go) -```go -package poll - -import ( - "std" - - "gno.land/p/demo/avl" -) - -// Main struct -type Poll struct { - title string - description string - deadline int64 // block height - voters *avl.Tree // addr -> yes / no (bool) -} - -// Getters -func (p Poll) Title() string { - return p.title -} - -func (p Poll) Description() string { - return p.description -} - -func (p Poll) Deadline() int64 { - return p.deadline -} - -func (p Poll) Voters() *avl.Tree { - return p.voters -} - -// Poll instance constructor -func NewPoll(title, description string, deadline int64) *Poll { - return &Poll{ - title: title, - description: description, - deadline: deadline, - voters: avl.NewTree(), - } -} - -// Vote Votes for a user -func (p *Poll) Vote(voter std.Address, vote bool) { - p.Voters().Set(voter.String(), vote) -} - -// HasVoted vote: yes - true, no - false -func (p *Poll) HasVoted(address std.Address) (bool, bool) { - vote, exists := p.Voters().Get(address.String()) - if exists { - return true, vote.(bool) - } - return false, false -} - -// VoteCount Returns the number of yay & nay votes -func (p Poll) VoteCount() (int, int) { - var yay int - - p.Voters().Iterate("", "", func(key string, value interface{}) bool { - vote := value.(bool) - if vote == true { - yay = yay + 1 - } - - return false - }) - return yay, p.Voters().Size() - yay -} -``` - -View this code in the Playground [here](https://play.gno.land/p/dwARIIq0meB). - -A few remarks: - -- We are using the `std` library for accessing blockchain-related functionality -and types, such as `std.Address`. -- Since the `map` data type is not deterministic in Go, we need to use the AVL -tree structure, defined -under `gno.land/p/demo/avl`. It behaves similarly to a map; it maps a key of -type `string` onto a value of any type - `interface{}`. -- We are importing the `gno.land/p/demo/avl` package directly from on-chain storage. -You can find predeployed packages & libraries which provide additional Gno -functionality in the [Gno monorepo](https://github.com/gnolang/gno), under the `examples/` folder. - -:::info -After testing the `Poll` package, we need to deploy it in order to use it in our realm. -Check out the [deployment](deploy.md) guide to learn how to do this. -::: - -### Poll Factory Realm - -Moving on, we can create the Poll Factory realm. - -The realm will contain the following functionality: - -- An exported `NewPoll` method, to allow users to create polls -- An exported `Vote` method, to allow users to pledge votes for any active poll -- A `Render` function to display the realm state - -[embedmd]:# (../assets/how-to-guides/write-simple-dapp/poll-2.gno go) -```go -package poll - -import ( - "std" - - "gno.land/p/demo/avl" - "gno.land/p/demo/poll" - "gno.land/p/demo/seqid" - "gno.land/p/demo/ufmt" -) - -// state variables -var ( - polls *avl.Tree // id -> Poll - pollIDCounter seqid.ID -) - -func init() { - polls = avl.NewTree() -} - -// NewPoll - Creates a new Poll instance -func NewPoll(title, description string, deadline int64) string { - // get block height - if deadline <= std.GetHeight() { - panic("deadline has to be in the future") - } - - // Generate int - id := pollIDCounter.Next().String() - p := poll.NewPoll(title, description, deadline) - - // add new poll in avl tree - polls.Set(id, p) - - return ufmt.Sprintf("Successfully created poll #%s!", id) -} - -// Vote - vote for a specific Poll -// yes - true, no - false -func Vote(id string, vote bool) string { - // get txSender - txSender := std.GetOrigCaller() - - // get specific Poll from AVL tree - pollRaw, exists := polls.Get(id) - - if !exists { - panic("poll with specified doesn't exist") - } - - // cast Poll into proper format - poll, _ := pollRaw.(*poll.Poll) - - voted, _ := poll.HasVoted(txSender) - if voted { - panic("you've already voted!") - } - - if poll.Deadline() <= std.GetHeight() { - panic("voting for this poll is closed") - } - - // record vote - poll.Vote(txSender, vote) - - // update Poll in tree - polls.Set(id, poll) - - if vote == true { - return ufmt.Sprintf("Successfully voted YAY for poll #%s!", id) - } - return ufmt.Sprintf("Successfully voted NAY for poll #%s!", id) -} -``` - -:::info -Depending on where you deployed your `Poll` package, you will have to change its -import path in the realm code. -::: - -With that we have written the core functionality of the realm, and all that is left is -the [Render function](../concepts/realms.md). -Its purpose is to help us display the state of the realm in Markdown, by formatting the state into a string buffer: - - -[embedmd]:# (../assets/how-to-guides/write-simple-dapp/poll-3.gno go) -```go -func Render(path string) string { - var b bytes.Buffer - - b.WriteString("# Polls!\n\n") - - if polls.Size() == 0 { - b.WriteString("### No active polls currently!") - return b.String() - } - polls.Iterate("", "", func(key string, value interface{}) bool { - - // cast raw data from tree into Poll struct - p := value.(*poll.Poll) - ddl := p.Deadline() - - yay, nay := p.VoteCount() - yayPercent := 0 - nayPercent := 0 - - if yay+nay != 0 { - yayPercent = yay * 100 / (yay + nay) - nayPercent = nay * 100 / (yay + nay) - } - - b.WriteString( - ufmt.Sprintf( - "## Poll #%s: %s\n", - key, // poll ID - p.Title(), - ), - ) - - dropdown := "
\nPoll details
" - - b.WriteString(dropdown + "Description: " + p.Description()) - - b.WriteString( - ufmt.Sprintf("
Voting until block: %d
Current vote count: %d", - p.Deadline(), - p.Voters().Size()), - ) - - b.WriteString( - ufmt.Sprintf("
YAY votes: %d (%d%%)", yay, yayPercent), - ) - b.WriteString( - ufmt.Sprintf("
NAY votes: %d (%d%%)
", nay, nayPercent), - ) - - dropdown = "
\nVote details" - b.WriteString(dropdown) - - p.Voters().Iterate("", "", func(key string, value interface{}) bool { - - voter := key - vote := value.(bool) - - if vote == true { - b.WriteString( - ufmt.Sprintf("
%s voted YAY!", voter), - ) - } else { - b.WriteString( - ufmt.Sprintf("
%s voted NAY!", voter), - ) - } - return false - }) - - b.WriteString("
\n\n") - return false - }) - return b.String() -} -``` - -View this code in the Playground [here](https://play.gno.land/p/5jgHw29sGq4). - -To see how to deploy this app, visit the [Deployment guide](./deploy.md). - -## Conclusion - -That's it 🎉 - -You have successfully built a simple but fully-fledged dApp using Gno! -Now you're ready to conquer new, more complex dApps in Gno. diff --git a/docs/legal/README.md b/docs/legal/README.md deleted file mode 100644 index aa93c1acf5e..00000000000 --- a/docs/legal/README.md +++ /dev/null @@ -1,7 +0,0 @@ - * [eris_agreement.pdf](./eris_agreement.pdf) - This 2015 agreement was - necessary to publish Tendermint under Apache2.0. Since then Eris Industries - has become Monax. Ethan Buchman at the time was an external contributor - from Eris Industries to the Tendermint project. Due to intermixing of - contributions without a clear license agreement, our relative contributions - had to be split out to convince Eris to allow us to publish everything under - Apache2.0. diff --git a/docs/legal/eris_agreement.pdf b/docs/legal/eris_agreement.pdf deleted file mode 100644 index 41bcff05a44..00000000000 Binary files a/docs/legal/eris_agreement.pdf and /dev/null differ diff --git a/docs/overview.md b/docs/overview.md deleted file mode 100644 index a687c878dde..00000000000 --- a/docs/overview.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -id: overview -slug: / -description: "gno.land is a Layer 1 blockchain platform that enables the execution of Smart Contracts using an interpreted -version of the Go programming language called Gno." ---- - -# Overview - -## What is gno.land? - -gno.land is a Layer 1 blockchain platform that enables the execution of Smart Contracts using an interpreted -version of the Go programming language called Gno. - -### Key Features and Technology - -1. **Interpreted Gno**: gno.land utilizes the Gno programming language, which is based on Go. It is executed - through a specialized virtual machine called the GnoVM, purpose-built for blockchain development with built-in - determinism and a modified standard library. While Gno - shares similarities with Go in terms of syntax, it currently lacks go routine support. However, this feature is - planned for future development, ensuring deterministic GnoVM executions. -2. **Consensus Protocol - Tendermint2**: gno.land achieves consensus between blockchain nodes using the Tendermint2 - consensus protocol. This approach ensures secure and reliable network operation. -3. **Inter-Blockchain Communication (IBC)**: In the future, gno.land will be able to communicate and exchange data with - other blockchain networks within the Cosmos ecosystem through the Inter-Blockchain Communication (IBC) protocol. - -### Why Go-based? - -The decision to base gno.land's language on Go was influenced by the following factors: - -1. **Standard and Secure Language**: Go is a well-established and secure programming language, widely adopted in the - software development community. By leveraging Go's features, gno.land benefits from a robust and proven foundation. -2. **User-Friendly**: Go's simplicity and ease of understanding make it beginner-friendly. This accessibility lowers the - entry barrier for developers to create Smart Contracts on the gno.land platform. - -### How does it compare with Ethereum? - -In comparison to Ethereum, gno.land offers distinct advantages: - -1. **Transparent and Auditable Smart Contracts**: gno.land Smart Contracts are fully transparent and auditable by users - because the actual source code is uploaded to the blockchain. In contrast, Ethereum requires contracts to be - precompiled into bytecode, leading to less transparency as bytecode is stored on the blockchain, not the - human-readable source code. Smart contracts in gno.land can be used as libraries with a simple import statement, making - gno.land a defacto source-code repository for the ecosystem. - -2. **General-Purpose Language**: gno.land's Gno is a general-purpose language, similar to Go, extending its - usability beyond the context of blockchain. In contrast, Solidity is designed specifically for Smart Contracts on the - Ethereum platform. - -## Using the gno.land Documentation - -gno.land's documentation adopts the [Diataxis](https://diataxis.fr/) framework, ensuring structured and predictable content. It includes: -- A [Getting Started](getting-started/local-setup/local-setup.md) section, covering simple instructions on how to begin your journey into gno.land. -- Concise how-to guides for specific technical tasks. -- Conceptual explanations, offering context and usage insights. -- Detailed reference sections with implementation specifics. -- Tutorials aimed at beginners to build fundamental skills for developing in gno.land. diff --git a/docs/reference/gno-js-client/gno-js-client.md b/docs/reference/gno-js-client/gno-js-client.md deleted file mode 100644 index 90b480bdd69..00000000000 --- a/docs/reference/gno-js-client/gno-js-client.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -id: gno-js-client ---- - -# Gno JavaScript Client - -[@gnolang/gno-js-client](https://github.com/gnolang/gno-js-client) is a JavaScript/TypeScript client implementation -for Gno chains. It is an extension of the [tm2-js-client](https://github.com/gnolang/tm2-js-client), but with -Gno-specific functionality. - -## Key Features - -- Provides the ability to interact with Gno Realms / Packages -- Easy interaction with VM-specific ABCI queries - -## Installation - -To install `@gnolang/gno-js-client`, use your preferred package manager: - -```bash -yarn add @gnolang/gno-js-client -``` - -```bash -npm install @gnolang/gno-js-client -``` diff --git a/docs/reference/gno-js-client/gno-provider.md b/docs/reference/gno-js-client/gno-provider.md deleted file mode 100644 index c76bfebfe31..00000000000 --- a/docs/reference/gno-js-client/gno-provider.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -id: gno-js-provider ---- - -# Gno Provider - -The `Gno Provider` is an extension on the `tm2-js-client` `Provider`, -outlined [here](../tm2-js-client/Provider/provider.md). Both JSON-RPC and WS providers are included with the package. - -## Instantiation - -### new GnoWSProvider - -Creates a new instance of the Gno WebSocket Provider, based on [`tm2-js-client` `WSProvider`](../tm2-js-client/Provider/ws-provider.md). - -#### Parameters - -Same as [`tm2-js-client` `WSProvider`](../tm2-js-client/Provider/ws-provider.md). - -#### Usage - -```ts -new GnoWSProvider('ws://staging.gno.land:26657/ws'); -// provider with WS connection is created -``` - -### new GnoJSONRPCProvider - -Creates a new instance of the Gno JSON-RPC Provider, based on [`tm2-js-client` `JSONRPCProvider`](../tm2-js-client/Provider/json-rpc-provider.md). - -#### Parameters - -Same as [`tm2-js-client` `JSONRPCProvider`](../tm2-js-client/Provider/json-rpc-provider.md). - -#### Usage - -```ts -new GnoJSONRPCProvider('http://staging.gno.land:36657'); -// provider is created -``` - -## Realm Methods - -### getRenderOutput - -Executes the Render(path) method in read-only mode - -#### Parameters - -* `packagePath` **string** the gno package path -* `path` **string** the render path -* `height` **number** the height for querying. - If omitted, the latest height is used (optional, default `0`) - -Returns **Promise** - -#### Usage - -```ts -await provider.getRenderOutput('gno.land/r/demo/demo_realm', ''); -// ## Hello World! -``` - -### getFunctionSignatures - -Fetches public facing function signatures - -#### Parameters - -* `packagePath` **string** the gno package path -* `height` **number** the height for querying. - If omitted, the latest height is used (optional, default `0`) - -Returns **Promise** - -#### Usage - -```ts -await provider.getFunctionSignatures('gno.land/r/demo/foo20'); -/* -[ - { FuncName: 'TotalSupply', Params: null, Results: [ [Object] ] }, - { - FuncName: 'BalanceOf', - Params: [ [Object] ], - Results: [ [Object] ] - }, - { - FuncName: 'Allowance', - Params: [ [Object], [Object] ], - Results: [ [Object] ] - }, - { - FuncName: 'Transfer', - Params: [ [Object], [Object] ], - Results: null - }, - { - FuncName: 'Approve', - Params: [ [Object], [Object] ], - Results: null - }, - { - FuncName: 'TransferFrom', - Params: [ [Object], [Object], [Object] ], - Results: null - }, - { FuncName: 'Faucet', Params: null, Results: null }, - { FuncName: 'Mint', Params: [ [Object], [Object] ], Results: null }, - { FuncName: 'Burn', Params: [ [Object], [Object] ], Results: null }, - { FuncName: 'Render', Params: [ [Object] ], Results: [ [Object] ] } -] - */ -``` - -### evaluateExpression - -Evaluates any expression in readonly mode and returns the results - -#### Parameters - -* `packagePath` **string** the gno package path -* `expression` **string** the expression to be evaluated -* `height` **number** the height for querying. - If omitted, the latest height is used (optional, default `0`) - -Returns **Promise** - -#### Usage - -```ts -await provider.evaluateExpression('gno.land/r/demo/foo20', 'TotalSupply()') -// (10100000000 uint64) -``` - -### getFileContent - -Fetches the file content, or the list of files if the path is a directory - -#### Parameters - -* `packagePath` **string** the gno package path -* `height` **number** the height for querying. - If omitted, the latest height is used (optional, default `0`) - -Returns **Promise** - -#### Usage - -```ts -await provider.getFileContent('gno.land/r/demo/foo20') -/* -foo20.gno -foo20_test.gno - */ -``` diff --git a/docs/reference/gno-js-client/gno-wallet.md b/docs/reference/gno-js-client/gno-wallet.md deleted file mode 100644 index 247c3d52878..00000000000 --- a/docs/reference/gno-js-client/gno-wallet.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -id: gno-js-wallet ---- - -# Gno Wallet - -The `Gno Wallet` is an extension on the `tm2-js-client` `Wallet`, outlined [here](../tm2-js-client/wallet.md). - -## Account Methods - -### transferFunds - -Initiates a native currency transfer transaction between accounts - -#### Parameters - -* `to` **string** the bech32 address of the receiver -* `funds` **Map** the denomination -> value map for funds -* `fee` **TxFee** the custom transaction fee, if any - -Returns **Promise** - -#### Usage - -```ts -let fundsMap = new Map([ - ["ugnot", 10], -]); - -await wallet.transferFunds('g1flk9z2qmkgqeyrl654r3639rzgz7xczdfwwqw7', fundsMap); -// returns the transaction hash -``` - -### callMethod - -Invokes the specified method on a GNO contract - -#### Parameters - -* `path` **string** the gno package / realm path -* `method` **string** the method name -* `args` **string[]** the method arguments, if any -* `funds` **Map** the denomination -> value map for funds -* `fee` **TxFee** the custom transaction fee, if any - -Returns **Promise** - -#### Usage - -```ts -let fundsMap = new Map([ - ["ugnot", 10], -]); - -await wallet.callMethod('gno.land/r/demo/foo20', 'TotalBalance', []); -// returns the transaction hash -``` - -### deployPackage - -Deploys the specified package / realm - -#### Parameters - -* `gnoPackage` **MemPackage** the package / realm to be deployed -* `funds` **Map** the denomination -> value map for funds -* `fee` **TxFee** the custom transaction fee, if any - -Returns **Promise** - -#### Usage - -```ts -const memPackage: MemPackage = // ... - - await wallet.deployPackage(memPackage); -// returns the transaction hash -``` diff --git a/docs/reference/gnoclient/gnoclient.md b/docs/reference/gnoclient/gnoclient.md deleted file mode 100644 index 672a3772bb7..00000000000 --- a/docs/reference/gnoclient/gnoclient.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -id: gnoclient ---- - -# Gnoclient - -[Gnoclient](https://github.com/gnolang/gno/tree/master/gno.land/pkg/gnoclient) -allows you to easily access Gno blockchains from your Go code, through exposing -APIs for common functionality. - -## Key Features - -- Connect to a Gno chain via RPC -- Use local keystore to sign & broadcast transactions containing any type of -Gno message -- Sign & broadcast transactions with batch messages -- Use [ABCI queries](../../gno-tooling/cli/gnokey/querying-a-network.md) in -your Go code - -## Installation - -To add Gnoclient to your Go project, run the following command: -```bash -go get github.com/gnolang/gno/gno.land/pkg/gnoclient -``` - -## Reference documentation & usage - -To see the full reference documentation for the `gnoclient` package, we recommend -visiting the [`gnoclient godoc page`](https://gnolang.github.io/gno/github.com/gnolang/gno@v0.0.0/gno.land/pkg/gnoclient.html). - -For a tutorial on how to use the `gnoclient` package, check out -["How to connect a Go app to gno.land"](../../how-to-guides/connecting-from-go.md). - diff --git a/docs/reference/network-config.md b/docs/reference/network-config.md deleted file mode 100644 index 45a56b772ae..00000000000 --- a/docs/reference/network-config.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -id: network-config ---- - -# Network configurations - -| Network | RPC Endpoint | Chain ID | -|-------------|----------------------------------|---------------| -| Portal Loop | https://rpc.gno.land:443 | `portal-loop` | -| Test5 | https://rpc.test5.gno.land:443 | `test5` | -| Test4 | https://rpc.test4.gno.land:443 | `test4` | -| Staging | https://rpc.staging.gno.land:443 | `staging` | - -### WebSocket endpoints -All networks follow the same pattern for websocket connections: - -```shell -wss:///websocket -``` diff --git a/docs/reference/reference.md b/docs/reference/reference.md deleted file mode 100644 index a635370c62f..00000000000 --- a/docs/reference/reference.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -id: reference ---- - -# Reference - -Welcome to the **Reference** section for Gno. This section outlines common APIs, -network configurations, client usages, etc. diff --git a/docs/reference/rpc-endpoints.md b/docs/reference/rpc-endpoints.md deleted file mode 100644 index ecbbde8262b..00000000000 --- a/docs/reference/rpc-endpoints.md +++ /dev/null @@ -1,503 +0,0 @@ ---- -id: rpc-endpoints ---- - -# Gno RPC Endpoints - -For network configurations, view the [network configuration page](./network-config.md). -## Common Parameters - -#### Response - -| Name | Type | Description | -| --------------- | ------ | --------------------------------- | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | Object | (upon success) The result object. | -| `error` | Object | (upon failure) The error object. | -| `error.code` | Number | The error code. | -| `error.message` | String | The error message. | -| `error.data` | String | The error data. | - -## Health Check - -Call with the `/health` path when verifying that the node is running. - -#### Response - -| Name | Type | Description | -| --------- | ------ | ---------------- | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | Object | {} | - -## Check Node Server Status - -Call with the `/status` path to check the information from a node. - -#### Response - -| Name | Type | Description | -| --------- | ---------------- | ------------------------------------- | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Status Result] | The result of the node server status. | - -#### Status Result - -| Name | Type | Description | -| ---------------- | ------ | ----------------------------------- | -| `node_info` | Object | General information about the node. | -| `sync_info` | Object | The sync information. | -| `validator_info` | Object | The validator information. | - -## Get Network Information - -Call with the `/net_info` path to check the network information from the node. - -#### Response - -| Name | Type | Description | -| --------- | ----------------- | ------------------------ | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[NetInfo Result] | The network information. | - -#### NetInfo Result - -| Name | Type | Description | -| ----------- | ---------- | ------------------ | -| `listening` | Boolean | Enables listening. | -| `listeners` | String \[] | List of listeners. | -| `n_peers` | String | Number of peers. | -| `peers` | String \[] | List of peers. | - -## Get Genesis Block Information - -Call with the `/genesis` path to retrieve information about the Genesis block from the node. - -#### Response - -| name | Type | Description | -| --------- | ------ | ------------------------------ | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | Object | The Genesis block information. | - -## Get Consensus Parameters - -Call with the /consensus\_params path to check the consensus algorithm parameters at the specified height. - -#### Parameters - -| Name | Description | -| -------- | ----------------- | -| `height` | The block height. | - -#### Response - -| Name | Type | Description | -| --------- | -------------------------- | ------------------------------------ | -| `jsonrpc` | String | The RPC Version. | -| `id` | String | The response ID. | -| `result` | \[Consensus Params Result] | The consensus parameter information. | - -#### Consensus Params Result - -| Name | Type | Description | -| ----------------------------- | ------ | -------------------------- | -| `block_height` | String | The block height. | -| `consensus_params` | Object | The parameter information. | -| `consensus_params.Block` | Object | The block parameters. | -| `consensus_params.Validator` | Object | The validator parameters. | - -## Get Consensus State - -Call with the `/consensus_state` to get the consensus state of the Gnoland blockchain - -#### Response - -| Name | Type | Description | -| ------- | --------------------------- | -------------------------------- | -| jsonrpc | String | The RPC version. | -| id | String | The response ID. | -| result | \[Consensus State Response] | The consensus state information. | - -#### Consensus State Response - -| Name | Type | Description | -| --------------------------------- | ------ | -------------------------------- | -| `round_state` | Object | The consensus state object. | -| `round_state.height/round/step` | String | The block height / round / step. | -| `round_state.start_time` | String | The round start time. | -| `round_state.proposal_block_hash` | String | The proposal block hash. | -| `round_state.locked_block_hash` | String | The locked block hash. | -| `round_state.valid_block_hash` | String | The valid block hash. | -| `round_state.height_vote_set` | Object | - | - -## Get Commit - -Call with the `/commit` path to retrieve commit information at the specified block height. - -#### Parameters - -| Name | Description | -| -------- | ----------------- | -| `height` | The block height. | - -#### Response - -| Name | Type | Description | -| --------- | ---------------- | ----------------------- | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Commit Result] | The commit information. | - -#### Commit Result - -| Name | Type | Description | -| -------------- | ------- | ------------------------- | -| signed\_header | Object | The signed header object. | -| canonical | Boolean | Returns commit state. | - -## Get Block Information - -Call with the `/block` path to retrieve block information at the specified height. - -#### Parameters - -| Name | Description | -| -------- | ----------------- | -| `height` | The block height. | - -#### Response - -| Name | Type | Description | -| --------- | --------------- | ----------------------- | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Block Result] | The commit information. | - -#### Block Result - -| Name | Type | Description | -| ------------ | ------ | ---------------------- | -| `block_meta` | Object | The block metadata. | -| `block` | Object | The block information. | - -## Get Block Results - -Call with the `/block_results` path to retrieve block processing information at the specified height. - -#### Parameters - -| Name | Description | -| -------- | ----------------- | -| `height` | The block height. | - -#### Response - -| Name | Type | Description | -| --------- | --------------- | ------------------ | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Block Result] | The result object. | - -#### Block Result - -| Name | Type | Description | -| --------- | ------------------------ | ------------------------------------- | -| `height` | Object | The block height. | -| `results` | \[Block Result Info] \[] | The list of block processing results. | - -#### Block Result Info - -| Name | Type | Description | -| --------------------------- | ---------- | -------------------------------- | -| `deliver_tx` | Object \[] | The list of transaction results. | -| `deliver_tx[].ResponseBase` | Object | The transaction response object. | -| `deliver_tx[].GasWanted` | String | Maximum amount of gas to use. | -| `deliver_tx[].GasUsed` | String | Actual gas used. | -| `begin_block` | Object | Previous block information. | -| `end_block` | Object | Next block information. | - -## Get Block List - -Call with the `/blockchain` path to retrieve information about blocks within a specified range. - -#### Parameters - -| Name | Description | -| ----------- | ------------------------- | -| `minHeight` | The minimum block height. | -| `maxHeight` | The maximum block height. | - -#### Response - -| Name | Type | Description | -| --------- | -------------------- | ------------------ | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Blockchain Result] | The result object. | - -#### Blockchain Result - -| Name | Type | Description | -| ------------- | ---------- | --------------------------- | -| `last_height` | String | The latest block height. | -| `block_meta` | Object \[] | The list of block metadata. | - -## Get a No. of Unconfirmed Transactions - -Call with the `/num_unconfirmed_txs` path to get data about unconfirmed transactions. - -#### Response - -| Name | Type | Description | -| --------- | ----------------------------- | ------------------ | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Num Unconfirmed Txs Result] | The result object. | - -#### Num Unconfirmed Txs Result - -| Name | Type | Description | -| ------------- | ------ | --------------------------- | -| `n_txs` | String | The number of transactions. | -| `total` | String | The total number. | -| `total_bytes` | String | Total bytes. | -| `txs` | null | - | - -## Get a List of Unconfirmed Transactions - -Call with the `/unconfirmed_txs` path to get a list of unconfirmed transactions. - -#### Parameters - -| Name | Description | -| ------- | --------------------------------------- | -| `limit` | The maximum transaction numbers to get. | - -#### Response - -| Name | Type | Description | -| --------- | ------------------------- | ------------------ | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Unconfirmed Txs Result] | The result object. | - -#### Unconfirmed Txs Result - -| Name | Type | Description | -| ------------- | ---------- | ----------------------------------- | -| `n_txs` | String | The number of transactions. | -| `total` | String | The total number. | -| `total_bytes` | String | Total bytes. | -| `txs` | Object \[] | A list of unconfirmed transactions. | - -## Get a List of Validators - -Call with the `/validators` path to get a list of validators at a specific height. - -#### Parameters - -| Name | Description | -| -------- | ----------------------------------------- | -| `height` | The block height (default: newest block). | - -#### Response - -| Name | Type | Description | -| --------- | -------------------- | ------------------ | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Validators Result] | The result object. | - -#### Validators Result - -| Name | Type | Description | -| -------------- | ---------------- | ----------------------- | -| `block_height` | Object | The block height. | -| `validators` | \[Validator] \[] | The list of validators. | - -#### Validator - -| Name | Type | Description | -| ------------------- | ---------- | ---------------------------------------- | -| `address` | String | The address of the validator. | -| `pub_key` | Object \[] | The public key object of the validator. | -| `pub_key.@type` | String | The type of validator's public key. | -| `pub_key.value` | String | The value of the validator's public key. | -| `voting_power` | String | Voting power of the validator. | -| `proposer_priority` | String | The priority of the proposer. | - -## Broadcast a Transaction - Asynchronous - -Call with the `/broadcast_tx_async` path to create and broadcast a transaction without waiting for the transaction response. - -#### Parameters - -| Name | Description | -| ---- | ------------------------------------------- | -| `tx` | The value of the signed transaction binary. | - -#### Response - -| Name | Type | Description | -| --------- | --------------------- | ------------------ | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Transaction Result] | The result object. | - -#### Transaction Result - -| Name | Type | Description | -| ----- | ------ | ---------------------------- | -| hash | String | The transaction hash. | -| data | Object | The transaction data object. | -| error | Object | The error object. | -| log | String | The log information. | - -## Broadcast a Transaction - Synchronous - -Call with the `/broadcast_tx_sync` path to create and broadcast a transaction, then wait for the transaction response. - -#### Parameters - -| Name | Description | -| ---- | ------------------------------------------- | -| `tx` | The value of the signed transaction binary. | - -#### Response - -| Name | Type | Description | -| --------- | --------------------- | ------------------ | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Transaction Result] | The result object. | - -#### Transaction Result - -| Name | Type | Description | -| ----- | ------ | ---------------------------- | -| hash | String | The transaction hash. | -| data | Object | The transaction data object. | -| error | Object | The error object. | -| log | String | The log information. | - -## (NOT RECOMMENDED) Broadcast Transaction and Get Commit Information - -Call with the `/broadcast_tx_commit` path to create and broadcast a transaction, then wait for the transaction response and the commit response. - -#### Parameters - -| Name | Description | -| ---- | ------------------------------------------- | -| `tx` | The value of the signed transaction binary. | - -#### Response - -| Name | Type | Description | -| --------- | ---------------------------- | ------------------ | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[Transaction Commit Result] | The result object. | - -#### Transaction Commit Result - -| Name | Type | Description | -| ------------ | ------ | ----------------------------------------------------------- | -| `height` | String | The height of the block when the transaction was committed. | -| hash | String | The transaction hash. | -| `deliver_tx` | Object | The delivered transaction information. | -| `check_tx` | Object | The committed transaction information. | - -## ABCI - -### Get ABCI Information - -Call with the `/abci_info` path to get the latest information about the ABCI. - -#### Response - -| Name | Type | Description | -| --------- | ------------------- | ----------------------- | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[ABCI Info Result] | The commit information. | - -#### ABCI Info Result - -| Name | Type | Description | -| --------------------------- | ---------------- | -------------------------- | -| `response` | Object | The metadata of the block. | -| `response.ResponseBase` | \[ABCI Response] | The ABCI response data. | -| `response.ABCIVersion` | String | The ABCI version. | -| `response.AppVersion` | String | The app version. | -| `response.LastBlockHeight` | String | The latest block height. | -| `response.LastBlockAppHash` | String | The latest block hash. | - -#### ABCI Response - -| Name | Type | Description | -| ------ | ---------- | --------------------------------- | -| Data | String | The Base64-encoded response data. | -| Error | Object | The ABCI response error object. | -| Events | Object \[] | The list of event objects. | -| Log | String | The ABCI response log. | -| Info | String | The ABCI response information. | - -### Get ABCI Query - -Call with the `/abci_query` to get information via the ABCI Query. - -#### Query - -| Name | Description | -| ------------------------- | ------------------------------------------------------------------ | -| `auth/accounts/{ADDRESS}` | Returns the account information. | -| `bank/balances/{ADDRESS}` | Returns the balance information about the account. | -| `vm/qfuncs` | Returns public facing function signatures as JSON. | -| `vm/qfile` | Returns the file bytes, or list of files if directory. | -| `vm/qrender` | Calls `.Render()` in readonly mode. | -| `vm/qeval` | Evaluates any expression in readonly mode and returns the results. | -| `vm/store` | (not yet supported) Fetches items from the store. | -| `vm/package` | (not yet supported) Fetches a package's files. | - -#### Parameters - -| Name | Description | -| ------------------- | ------------------------------------------------ | -| `path` | The query path. | -| `data` | The data from the query path. | -| (optional) `height` | The block height (default: latest block height). | -| (optional) `prove` | The validation status. | - -#### Response - -| Name | Type | Description | -| --------- | -------------------- | ----------------------- | -| `jsonrpc` | String | The RPC version. | -| `id` | String | The response ID. | -| `result` | \[ABCI Query Result] | The commit information. | - -#### ABCI Query Result - -| Name | Type | Description | -| ----------------------- | ---------------- | -------------------------- | -| `response` | Object | The metadata of the block. | -| `response.ResponseBase` | \[ABCI Response] | The ABCI response data. | -| `response.Key` | String | The key. | -| `response.Value` | String | The value. | -| `response.Proof` | String | The validation ID. | -| `response.Height` | String | The block height. | - -#### ABCI Response - -| Name | Type | Description | -| ------ | ---------- | --------------------------------- | -| Data | String | The Base64-encoded response data. | -| Error | Object | The ABCI response error object. | -| Events | Object \[] | The list of event objects. | -| Log | String | The ABCI response log. | -| Info | String | The ABCI response information. | diff --git a/docs/reference/stdlibs/std/address.md b/docs/reference/stdlibs/std/address.md deleted file mode 100644 index 707540740a9..00000000000 --- a/docs/reference/stdlibs/std/address.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -id: address ---- - -# Address -Native address type in Gno, conforming to the Bech32 format. - -```go -type Address string -func (a Address) String() string {...} -func (a Address) IsValid() bool {...} -``` - -## String -Get **string** representation of **Address**. - -#### Usage -```go -stringAddr := addr.String() -``` - ---- -## IsValid -Check if **Address** is of a valid format. - -#### Usage -```go -if !address.IsValid() {...} -``` diff --git a/docs/reference/stdlibs/std/banker.md b/docs/reference/stdlibs/std/banker.md deleted file mode 100644 index b60b55ee93b..00000000000 --- a/docs/reference/stdlibs/std/banker.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -id: banker ---- - -# Banker -View concept page [here](../../../concepts/stdlibs/banker.md). - -```go -type BankerType uint8 - -const ( - BankerTypeReadonly BankerType = iota - BankerTypeOrigSend - BankerTypeRealmSend - BankerTypeRealmIssue -) - -type Banker interface { - GetCoins(addr Address) (dst Coins) - SendCoins(from, to Address, coins Coins) - IssueCoin(addr Address, denom string, amount int64) - RemoveCoin(addr Address, denom string, amount int64) -} -``` - -## GetBanker -Returns `Banker` of the specified type. - -#### Parameters -- `BankerType` - type of Banker to get: - - `BankerTypeReadonly` - read-only access to coin balances - - `BankerTypeOrigSend` - full access to coins sent with the transaction that calls the banker - - `BankerTypeRealmSend` - full access to coins that the realm itself owns, including the ones sent with the transaction - - `BankerTypeRealmIssue` - able to issue new coins - -#### Usage - -```go -banker := std.GetBanker(std.) -``` - -:::info `Banker` methods expect qualified denomination of the coins. Read more [here](./realm.md#coindenom). -::: - ---- - -## GetCoins -Returns `Coins` owned by `Address`. - -#### Parameters -- `addr` **Address** to fetch balances for - -#### Usage - -```go -coins := banker.GetCoins(addr) -``` ---- - -## SendCoins -Sends `coins` from address `from` to address `to`. `coins` needs to be a well-defined -`Coins` slice. - -#### Parameters -- `from` **Address** to send from -- `to` **Address** to send to -- `coins` **Coins** to send - -#### Usage -```go -banker.SendCoins(from, to, coins) -``` ---- - -## IssueCoin -Issues `amount` of coin with a denomination `denom` to address `addr`. - -#### Parameters -- `addr` **Address** to issue coins to -- `denom` **string** denomination of coin to issue -- `amount` **int64** amount of coin to issue - -#### Usage -```go -banker.IssueCoin(addr, denom, amount) -``` ---- - -## RemoveCoin -Removes (burns) `amount` of coin with a denomination `denom` from address `addr`. - -#### Parameters -- `addr` **Address** to remove coins from -- `denom` **string** denomination of coin to remove -- `amount` **int64** amount of coin to remove - -#### Usage -```go -banker.RemoveCoin(addr, denom, amount) -``` diff --git a/docs/reference/stdlibs/std/chain.md b/docs/reference/stdlibs/std/chain.md deleted file mode 100644 index 6a1da6483fd..00000000000 --- a/docs/reference/stdlibs/std/chain.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -id: chain ---- - -# Chain-related - -## IsOriginCall -```go -func IsOriginCall() bool -``` -Checks if the caller of the function is an EOA. Returns **true** if caller is an EOA, **false** otherwise. - -#### Usage -```go -if !std.IsOriginCall() {...} -``` ---- - -## AssertOriginCall -```go -func AssertOriginCall() -``` -Panics if caller of function is not an EOA. - -#### Usage -```go -std.AssertOriginCall() -``` ---- - -## ChainDomain -```go -func ChainDomain() string -``` -Returns the chain domain. Currently only `gno.land` is supported. - -#### Usage -```go -domain := std.ChainDomain() // gno.land -``` ---- - -## Emit -```go -func Emit(typ string, attrs ...string) -``` -Emits a Gno event. Takes in a **string** type (event identifier), and an even number of string -arguments acting as key-value pairs to be included in the emitted event. - -#### Usage -```go -std.Emit("MyEvent", "myKey1", "myValue1", "myKey2", "myValue2") -``` ---- - -## GetChainID -```go -func GetChainID() string -``` -Returns the chain ID. - -#### Usage -```go -chainID := std.GetChainID() // dev | test5 | main ... -``` ---- - -## GetHeight -```go -func GetHeight() int64 -``` -Returns the current block number (height). - -#### Usage -```go -height := std.GetHeight() -``` ---- - -## GetOrigSend -```go -func GetOrigSend() Coins -``` -Returns the `Coins` that were sent along with the calling transaction. - -#### Usage -```go -coinsSent := std.GetOrigSend() -``` ---- - -## GetOrigCaller -```go -func GetOrigCaller() Address -``` -Returns the original signer of the transaction. - -#### Usage -```go -caller := std.GetOrigCaller() -``` ---- - -## GetOrigPkgAddr -```go -func GetOrigPkgAddr() string -``` -Returns the address of the first (entry point) realm/package in a sequence of realm/package calls. - -#### Usage -```go -origPkgAddr := std.GetOrigPkgAddr() -``` ---- - -## CurrentRealm -```go -func CurrentRealm() Realm -``` -Returns current [Realm](realm.md) object. - -#### Usage -```go -currentRealm := std.CurrentRealm() -``` ---- - -## PrevRealm -```go -func PrevRealm() Realm -``` -Returns the previous caller [realm](realm.md) (can be code or user realm). If caller is a -user realm, `pkgpath` will be empty. - -#### Usage -```go -prevRealm := std.PrevRealm() -``` ---- - -## GetCallerAt -```go -func GetCallerAt(n int) Address -``` -Returns the n-th caller of the function, going back in the call trace. - -#### Usage -```go -currentRealm := std.GetCallerAt(1) // returns address of current realm -previousRealm := std.GetCallerAt(2) // returns address of previous realm/caller -std.GetCallerAt(0) // error, n must be > 0 -``` ---- - -## DerivePkgAddr -```go -func DerivePkgAddr(pkgPath string) Address -``` -Derives the Realm address from its `pkgpath` parameter. - -#### Usage -```go -realmAddr := std.DerivePkgAddr("gno.land/r/demo/tamagotchi") // g1a3tu874agjlkrpzt9x90xv3uzncapcn959yte4 -``` ---- - -## CoinDenom -```go -func CoinDenom(pkgPath, coinName string) string -``` -Composes a qualified denomination string from the realm's `pkgPath` and the provided coin name, e.g. `/gno.land/r/demo/blog:blgcoin`. This method should be used to get fully qualified denominations of coins when interacting with the `Banker` module. It can also be used as a method of the `Realm` object, Read more[here](./realm.md#coindenom). - -#### Parameters -- `pkgPath` **string** - package path of the realm -- `coinName` **string** - The coin name used to build the qualified denomination. Must start with a lowercase letter, followed by 2–15 lowercase letters or digits. - -#### Usage -```go -denom := std.CoinDenom("gno.land/r/demo/blog", "blgcoin") // /gno.land/r/demo/blog:blgcoin -``` diff --git a/docs/reference/stdlibs/std/coin.md b/docs/reference/stdlibs/std/coin.md deleted file mode 100644 index 3675e1365f6..00000000000 --- a/docs/reference/stdlibs/std/coin.md +++ /dev/null @@ -1,176 +0,0 @@ ---- -id: coin ---- - -# Coin -View concept page [here](../../../concepts/stdlibs/coin.md). - -```go -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} - -func NewCoin(denom string, amount int64) Coin {...} -func (c Coin) String() string {...} -func (c Coin) IsGTE(other Coin) bool {...} -func (c Coin) IsLT(other Coin) bool {...} -func (c Coin) IsEqual(other Coin) bool {...} -func (c Coin) Add(other Coin) Coin {...} -func (c Coin) Sub(other Coin) Coin {...} -func (c Coin) IsPositive() bool {...} -func (c Coin) IsNegative() bool {...} -func (c Coin) IsZero() bool {...} -``` - -## NewCoin -Returns a new Coin with a specific denomination and amount. - -#### Usage -```go -coin := std.NewCoin("ugnot", 100) -``` ---- - -## String -Returns a string representation of the `Coin` it was called upon. - -#### Usage -```go -coin := std.NewCoin("ugnot", 100) -coin.String() // 100ugnot -``` ---- - -## IsGTE -Checks if the amount of `other` Coin is greater than or equal than amount of -Coin `c` it was called upon. If coins compared are not of the same denomination, -`IsGTE` will panic. - -#### Parameters -- `other` **Coin** to compare with - -#### Usage -```go -coin1 := std.NewCoin("ugnot", 150) -coin2 := std.NewCoin("ugnot", 100) - -coin1.IsGTE(coin2) // true -coin2.IsGTE(coin1) // false -``` ---- - -## IsLT -Checks if the amount of `other` Coin is less than the amount of Coin `c` it was -called upon. If coins compared are not of the same denomination, `IsLT` will -panic. - -#### Parameters -- `other` **Coin** to compare with - -#### Usage -```go -coin := std.NewCoin("ugnot", 150) -coin := std.NewCoin("ugnot", 100) - -coin1.IsLT(coin2) // false -coin2.IsLT(coin1) // true -``` ---- - -## IsEqual -Checks if the amount of `other` Coin is equal to the amount of Coin `c` it was -called upon. If coins compared are not of the same denomination, `IsEqual` will -panic. - -#### Parameters -- `other` **Coin** to compare with - -#### Usage -```go -coin1 := std.NewCoin("ugnot", 150) -coin2 := std.NewCoin("ugnot", 100) -coin3 := std.NewCoin("ugnot", 100) - -coin1.IsEqual(coin2) // false -coin2.IsEqual(coin1) // false -coin2.IsEqual(coin3) // true -``` ---- - -## Add -Adds two coins of the same denomination. If coins are not of the same -denomination, `Add` will panic. If final amount is larger than the maximum size -of `int64`, `Add` will panic with an overflow error. Adding a negative amount -will result in subtraction. - -#### Parameters -- `other` **Coin** to add - -#### Usage -```go -coin1 := std.NewCoin("ugnot", 150) -coin2 := std.NewCoin("ugnot", 100) - -coin3 := coin1.Add(coin2) -coin3.String() // 250ugnot -``` ---- - -## Sub -Subtracts two coins of the same denomination. If coins are not of the same -denomination, `Sub` will panic. If final amount is smaller than the minimum size -of `int64`, `Sub` will panic with an underflow error. Subtracting a negative amount -will result in addition. - -#### Parameters -- `other` **Coin** to subtract - -#### Usage -```go -coin1 := std.NewCoin("ugnot", 150) -coin2 := std.NewCoin("ugnot", 100) - -coin3 := coin1.Sub(coin2) -coin3.String() // 50ugnot -``` ---- - -## IsPositive -Checks if a coin amount is positive. - -#### Usage -```go -coin1 := std.NewCoin("ugnot", 150) -coin2 := std.NewCoin("ugnot", -150) - -coin1.IsPositive() // true -coin2.IsPositive() // false -``` ---- - -## IsNegative -Checks if a coin amount is negative. - -#### Usage -```go -coin1 := std.NewCoin("ugnot", 150) -coin2 := std.NewCoin("ugnot", -150) - -coin1.IsNegative() // false -coin2.IsNegative() // true -``` ---- - -## IsZero -Checks if a coin amount is zero. - -#### Usage -```go -coin1 := std.NewCoin("ugnot", 150) -coin2 := std.NewCoin("ugnot", 0) - -coin1.IsZero() // false -coin2.IsZero() // true -``` - diff --git a/docs/reference/stdlibs/std/coins.md b/docs/reference/stdlibs/std/coins.md deleted file mode 100644 index cc366937153..00000000000 --- a/docs/reference/stdlibs/std/coins.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -id: coins ---- - -# Coins - -`Coins` is a set of `Coin`, one per denomination. - -```go -type Coins []Coin - -func NewCoins(coins ...Coin) Coins {...} -func (c Coins) String() string {...} -func (c Coins) AmountOf(denom string) int64 {...} -func (c Coins) Add(other Coins) Coins {...} -``` - -### NewCoins -Returns a new set of `Coins` given one or more `Coin`. Consolidates any denom -duplicates into one, keeping the properties of a mathematical set. - -#### Usage -```go -coin1 := std.NewCoin("ugnot", 150) -coin2 := std.NewCoin("example", 100) -coin3 := std.NewCoin("ugnot", 100) - -coins := std.NewCoins(coin1, coin2, coin3) -coins.String() // 250ugnot, 100example -``` ---- - -### String -Returns a string representation of the `Coins` set it was called upon. - -#### Usage -```go -coins := std.Coins{std.Coin{"ugnot", 100}, std.Coin{"foo", 150}, std.Coin{"bar", 200}} -coins.String() // 100ugnot,150foo,200bar -``` ---- - -### AmountOf -Returns **int64** amount of specified coin within the `Coins` set it was called upon. Returns `0` if the specified coin does not exist in the set. - -### Parameters -- `denom` **string** denomination of specified coin - -#### Usage -```go -coins := std.Coins{std.Coin{"ugnot", 100}, std.Coin{"foo", 150}, std.Coin{"bar", 200}} -coins.AmountOf("foo") // 150 -``` ---- - -### Add -Adds (or updates) the amount of specified coins in the `Coins` set. If the specified coin does not exist, `Add` adds it to the set. - -### Parameters -- `other` **Coins** to add to `Coins` set - -#### Usage -```go -coins := // ... -otherCoins := // ... -coins.Add(otherCoins) -``` diff --git a/docs/reference/stdlibs/std/realm.md b/docs/reference/stdlibs/std/realm.md deleted file mode 100644 index f69cd874c75..00000000000 --- a/docs/reference/stdlibs/std/realm.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -id: realm ---- - -# Realm -Structure representing a realm in Gno. See concept page [here](../../../concepts/realms.md). - -```go -type Realm struct { - addr Address - pkgPath string -} - -func (r Realm) Addr() Address {...} -func (r Realm) PkgPath() string {...} -func (r Realm) IsUser() bool {...} -func (r Realm) CoinDenom(coinName string) string {...} -``` - -## Addr -Returns the **Address** field of the realm it was called upon. - -#### Usage -```go -realmAddr := r.Addr() // eg. g1n2j0gdyv45aem9p0qsfk5d2gqjupv5z536na3d -``` ---- -## PkgPath -Returns the **string** package path of the realm it was called upon. - -#### Usage -```go -realmPath := r.PkgPath() // eg. gno.land/r/gnoland/blog -``` ---- -## IsUser -Checks if the realm it was called upon is a user realm. - -#### Usage -```go -if r.IsUser() {...} -``` ---- -## CoinDenom -Composes a qualified denomination string from the realm's `pkgPath` and the provided coin name, e.g. `/gno.land/r/demo/blog:blgcoin`. This method should be used to get fully qualified denominations of coins when interacting with the `Banker` module. - -#### Parameters -- `coinName` **string** - The coin name used to build the qualified denomination. Must start with a lowercase letter, followed by 2–15 lowercase letters or digits. - -#### Usage -```go -// in "gno.land/r/gnoland/blog" -denom := r.CoinDenom("blgcoin") // /gno.land/r/gnoland/blog:blgcoin -``` diff --git a/docs/reference/stdlibs/std/testing.md b/docs/reference/stdlibs/std/testing.md deleted file mode 100644 index 8a95ecf7827..00000000000 --- a/docs/reference/stdlibs/std/testing.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -id: testing ---- - -# Testing - -```go -func TestSkipHeights(count int64) -func TestSetOrigCaller(addr Address) -func TestSetOrigPkgAddr(addr Address) -func TestSetOrigSend(sent, spent Coins) -func TestIssueCoins(addr Address, coins Coins) -func TestSetRealm(realm Realm) -func NewUserRealm(address Address) Realm -func NewCodeRealm(pkgPath string) Realm -``` - ---- - -## TestSkipHeights - -```go -func TestSkipHeights(count int64) -``` -Modifies the block height variable by skipping **count** blocks. - -It also increases block timestamp by 5 seconds for every single count - -#### Usage -```go -std.TestSkipHeights(100) -``` ---- - -## TestSetOrigCaller - -```go -func TestSetOrigCaller(addr Address) -``` -Sets the current caller of the transaction to **addr**. - -#### Usage -```go -std.TestSetOrigCaller(std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")) -``` ---- - -## TestSetOrigPkgAddr - -```go -func TestSetOrigPkgAddr(addr Address) -``` -Sets the call entry realm address to **addr**. - -#### Usage -```go -std.TestSetOrigPkgAddr(std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec")) -``` - ---- - -## TestSetOrigSend - -```go -func TestSetOrigSend(sent, spent Coins) -``` -Sets the sent & spent coins for the current context. - -#### Usage -```go -std.TestSetOrigSend(sent, spent Coins) -``` ---- - -## TestIssueCoins - -```go -func TestIssueCoins(addr Address, coins Coins) -``` - -Issues testing context **coins** to **addr**. - -#### Usage - -```go -issue := std.Coins{{"coin1", 100}, {"coin2", 200}} -addr := std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") -std.TestIssueCoins(addr, issue) -``` - ---- - -## TestSetRealm - -```go -func TestSetRealm(rlm Realm) -``` - -Sets the realm for the current frame. After calling `TestSetRealm()`, calling -[`CurrentRealm()`](chain.md#currentrealm) in the same test function will yield the value of `rlm`, and -any `PrevRealm()` called from a function used after TestSetRealm will yield `rlm`. - -Should be used in combination with [`NewUserRealm`](#newuserrealm) & -[`NewCodeRealm`](#newcoderealm). - -#### Usage -```go -addr := std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") -std.TestSetRealm(std.NewUserRealm(addr)) -// or -std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) -``` - ---- - -## NewUserRealm - -```go -func NewUserRealm(address Address) Realm -``` - -Creates a new user realm for testing purposes. - -#### Usage -```go -addr := std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") -userRealm := std.NewUserRealm(addr) -``` - ---- - -## NewCodeRealm - -```go -func NewCodeRealm(pkgPath string) Realm -``` - -Creates a new code realm for testing purposes. - -#### Usage -```go -path := "gno.land/r/demo/boards" -codeRealm := std.NewCodeRealm(path) -``` - - - - - - - diff --git a/docs/reference/stdlibs/stdlibs.md b/docs/reference/stdlibs/stdlibs.md deleted file mode 100644 index edcd9b7b7be..00000000000 --- a/docs/reference/stdlibs/stdlibs.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -id: stdlibs ---- - -# Standard Libraries - -This section serves as a reference to the standard libraries available in Gno. - -Gno is designed to offer features similar to those in Go. Therefore, this documentation -does not include in-depth references for libraries that have identical implementation details -as those in Go. If a standard library differs in any way from its usage or implementation in Go, -it will be documented in this section. diff --git a/docs/reference/tm2-js-client/Provider/json-rpc-provider.md b/docs/reference/tm2-js-client/Provider/json-rpc-provider.md deleted file mode 100644 index b7700e1d97c..00000000000 --- a/docs/reference/tm2-js-client/Provider/json-rpc-provider.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: tm2-js-json-rpc-provider ---- - -# JSON-RPC Provider - -Provider based on JSON-RPC HTTP requests. - -### new JSONRPCProvider - -Creates a new instance of the JSON-RPC Provider - -#### Parameters - -* `baseURL` **string** the JSON-RPC URL of the node - -#### Usage - -```ts -new JSONRPCProvider('http://staging.gno.land:36657'); -// provider is created -``` diff --git a/docs/reference/tm2-js-client/Provider/provider.md b/docs/reference/tm2-js-client/Provider/provider.md deleted file mode 100644 index da6168eb6c2..00000000000 --- a/docs/reference/tm2-js-client/Provider/provider.md +++ /dev/null @@ -1,443 +0,0 @@ ---- -id: tm2-js-provider ---- - -# Overview - -A `Provider` is an interface that abstracts the interaction with the Tendermint2 chain, making it easier for users to -communicate with it. Rather than requiring users to understand which endpoints are exposed, what their return types are, -and how they are parsed, the `Provider` abstraction handles all of this behind the scenes. It exposes useful API methods -that users can use and expects concrete types in return. - -Currently, the `tm2-js-client` package provides support for two Provider implementations: - -- [JSON-RPC Provider](json-rpc-provider.md): executes each call as a separate HTTP RPC call. -- [WS Provider](ws-provider.md): executes each call through an active WebSocket connection, which requires closing when - not needed anymore. - -## Account Methods - -### getBalance - -Fetches the denomination balance of the account - -#### Parameters - -* `address` **string** the bech32 address of the account -* `denomination` **string** the balance denomination (optional, default `ugnot`) -* `height` **number** the height for querying. - If omitted, the latest height is used (optional, default `0`) - -Returns **Promise** - -#### Usage - -```ts -await provider.getBalance('g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq', 'atom'); -// 100 -``` - -### getAccountSequence - -Fetches the account sequence - -#### Parameters - -* `address` **string** the bech32 address of the account -* `height` **number** the height for querying. - If omitted, the latest height is used. (optional, default `0`) - -Returns **Promise** - -#### Usage - -```ts -await provider.getAccountSequence('g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq'); -// 42 -``` - -### getAccountNumber - -Fetches the account number. Errors out if the account -is not initialized - -#### Parameters - -* `address` **string** the bech32 address of the account -* `height` **number** the height for querying. - If omitted, the latest height is used (optional, default `0`) - -Returns **Promise** - -#### Usage - -```ts -await provider.getAccountNumber('g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq'); -// 100 -``` - -## Block methods - -### getBlock - -Fetches the block at the specific height, if any - -#### Parameters - -* `height` **number** the height for querying - -Returns **Promise** - -#### Usage - -```ts -await provider.getBlock(1); -/* -{ - block_meta: { - block_id: { - hash: "TxHKEGxFm/4+D7gxOJdVUaR+xTDZzlPrCVXuVm7SqHw=", - parts: { - total: "1", - hash: "+dqI9oyngnnlKyno7y+RxCLEPA9FxWA/MmXyJ4uoJAY=" - } - }, - header: { - version: "v1.0.0-rc.0", - chain_id: "dev", - height: "1", - time: "2023-05-01T10:32:20.807541Z", - num_txs: "0", - total_txs: "0", - app_version: "", - last_block_id: { - hash: null, - parts: { - total: "0", - hash: null - } - }, - last_commit_hash: null, - data_hash: null, - validators_hash: "FnuBaDvDLg4FotGRcZAFvhLkEjkb+kNLaAZrAVhL5Aw=", - next_validators_hash: "FnuBaDvDLg4FotGRcZAFvhLkEjkb+kNLaAZrAVhL5Aw=", - consensus_hash: "uKhnXFmGUkxgQSJf17ogbYLNXDo3UEPwQvzddo4Vkuw=", - app_hash: null, - last_results_hash: null, - proposer_address: "g1vsqzyy9a4h9ah8cxzkaw09rpzy369mkl70lfdk" - } - }, - block: { - header: { - version: "v1.0.0-rc.0", - chain_id: "dev", - height: "1", - time: "2023-05-01T10:32:20.807541Z", - num_txs: "0", - total_txs: "0", - app_version: "", - last_block_id: { - hash: null, - parts: { - total: "0", - hash: null - } - }, - last_commit_hash: null, - data_hash: null, - validators_hash: "FnuBaDvDLg4FotGRcZAFvhLkEjkb+kNLaAZrAVhL5Aw=", - next_validators_hash: "FnuBaDvDLg4FotGRcZAFvhLkEjkb+kNLaAZrAVhL5Aw=", - consensus_hash: "uKhnXFmGUkxgQSJf17ogbYLNXDo3UEPwQvzddo4Vkuw=", - app_hash: null, - last_results_hash: null, - proposer_address: "g1vsqzyy9a4h9ah8cxzkaw09rpzy369mkl70lfdk" - }, - data: { - txs: null - }, - last_commit: { - block_id: { - hash: null, - parts: { - total: "0", - hash: null - } - }, - precommits: null - } - } -} -*/ -``` - -### getBlockResult - -Fetches the block at the specific height, if any - -#### Parameters - -* `height` **number** the height for querying - -Returns **Promise** - -#### Usage - -```ts -await provider.getBlockResult(1); -/* -{ - height: "1", - results: { - deliver_tx: null, - end_block: { - ResponseBase: { - Error: null, - Data: null, - Events: null, - Log: "", - Info: "" - }, - ValidatorUpdates: null, - ConsensusParams: null, - Events: null - }, - begin_block: { - ResponseBase: { - Error: null, - Data: null, - Events: null, - Log: "", - Info: "" - } - } - } -} -*/ -``` - -### getBlockNumber - -Fetches the latest block number from the chain - -Returns **Promise** - -#### Usage - -```ts -await provider.getBlockNumber(); -// 1300 -``` - -## Network methods - -### getNetwork - -Fetches the network information - -Returns **Promise** - -#### Usage - -```ts -await provider.getNetwork(); -/* -{ - listening: true, - listeners: [ - "Listener(@)" - ], - n_peers: "0", - peers: [] -} -*/ -``` - -### getConsensusParams - -Fetches the consensus params for the specific block height - -#### Parameters - -* `height` **number** the height for querying - -Returns **Promise** - -#### Usage - -```ts -await provider.getConsensusParams(1); -/* -{ - block_height: "1", - consensus_params: { - Block: { - MaxTxBytes: "1000000", - MaxDataBytes: "2000000", - MaxBlockBytes: "0", - MaxGas: "10000000", - TimeIotaMS: "100" - }, - Validator: { - PubKeyTypeURLs: [ - "/tm.PubKeyEd25519" - ] - } - } -} -*/ -``` - -### getStatus - -Fetches the current node status - -Returns **Promise** - -#### Usage - -```ts -await provider.getStatus(); -/* -{ - node_info: { - version_set: [ - { - Name: "abci", - Version: "v1.0.0-rc.0", - Optional: false - }, - { - Name: "app", - Version: "", - Optional: false - }, - { - Name: "bft", - Version: "v1.0.0-rc.0", - Optional: false - }, - { - Name: "blockchain", - Version: "v1.0.0-rc.0", - Optional: false - }, - { - Name: "p2p", - Version: "v1.0.0-rc.0", - Optional: false - } - ], - net_address: "g1z0wa6rspsshkm2k7jlqvnjs8jdt4kvg4e9j640@0.0.0.0:26656", - network: "dev", - software: "", - version: "v1.0.0-rc.0", - channels: "QCAhIiMw", - moniker: "voyager.lan", - other: { - tx_index: "off", - rpc_address: "tcp://127.0.0.1:26657" - } - }, - sync_info: { - latest_block_hash: "x5ewEBhf9+MGXbEFkUdOm3RsE40D+plUia2u0PuVfHs=", - latest_app_hash: "7dB/+EmqLqEX2RkH2Zx+GcFo8c2vTs2ttW8urYyyFT4=", - latest_block_height: "55", - latest_block_time: "2023-05-06T11:28:35.643575Z", - catching_up: false - }, - validator_info: { - address: "g1vsqzyy9a4h9ah8cxzkaw09rpzy369mkl70lfdk", - pub_key: { - "@type": "/tm.PubKeyEd25519", - value: "X8ZS1DYu1eJ3HYnZ0OWk+0GgCdI7zA++kgWiprWMs3w=" - }, - voting_power: "0" - } -} -*/ -``` - -### getGasPrice - -**NOTE: Not supported yet** - -Fetches the current (recommended) average gas price - -Returns **Promise** - -### estimateGas - -**NOTE: Not supported yet** - -Estimates the gas limit for the transaction - -#### Parameters - -* `tx` **Tx** the transaction that needs estimating - -Returns **Promise** - -## Transaction methods - -### sendTransaction - -Sends the transaction to the node for committing and returns the transaction hash. -The transaction needs to be signed beforehand. - -#### Parameters - -* `tx` **string** the base64-encoded signed transaction - -Returns **Promise** - -#### Usage - -```ts -await provider.sendTransaction('ZXhhbXBsZSBzaWduZWQgdHJhbnNhY3Rpb24'); -// "dHggaGFzaA==" -``` - -### waitForTransaction - -Waits for the transaction to be committed on the chain. -NOTE: This method will not take in the fromHeight parameter once -proper transaction indexing is added - the implementation should -simply try to fetch the transaction first to see if it's included in a block -before starting to wait for it; Until then, this method should be used -in the sequence: -get latest block -> send transaction -> waitForTransaction(block before send) - -#### Parameters - -* `hash` **string** The transaction hash -* `fromHeight` **number** The block height used to begin the search (optional, default `latest`) -* `timeout` **number** Optional wait timeout in MS (optional, default `15000`) - -Returns **Promise** - -#### Usage - -```ts -await provider.waitForTransaction('ZXhhbXBsZSBzaWduZWQgdHJhbnNhY3Rpb24'); -/* -{ - messages:[], // should be filled with the appropriate message type - fee:{ - gasWanted: "100", - gasFee: "1ugnot" - }, - signatures:[ - { - pubKey:[ - { - type: "/tm.PubKeySecp256k1" - value: "X8ZS1DYu1eJ3HYnZ0OWk+0GgCdI7zA++kgWiprWMs3w=" - } - ], - signature: "X8ZS1DYu1eJ3HYnZ0OWk+0GgCdI7zA++kgWiprWMs3w=" - } - ], - memo: "check out gno.land!" -} -*/ -``` diff --git a/docs/reference/tm2-js-client/Provider/utility.md b/docs/reference/tm2-js-client/Provider/utility.md deleted file mode 100644 index 3f0181ccc39..00000000000 --- a/docs/reference/tm2-js-client/Provider/utility.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -id: tm2-js-utility ---- - -# Utility Helpers - -## Provider Helpers - -### extractBalanceFromResponse - -Extracts the specific balance denomination from the ABCI response - -#### Parameters - -* `abciData` **(string | null)** the base64-encoded ABCI data -* `denomination` **string** the required denomination - -### extractSequenceFromResponse - -Extracts the account sequence from the ABCI response - -#### Parameters - -* `abciData` **(string | null)** the base64-encoded ABCI data - -Returns **number** - -### extractAccountNumberFromResponse - -Extracts the account number from the ABCI response - -#### Parameters - -* `abciData` **(string | null)** the base64-encoded ABCI data - -Returns **number** - -### waitForTransaction - -Waits for the transaction to be committed to a block in the chain -of the specified provider. This helper does a search for incoming blocks -and checks if a transaction - -#### Parameters - -* `provider` **Provider** the provider instance -* `hash` **string** the base64-encoded hash of the transaction -* `fromHeight` **number** the starting height for the search. If omitted, it is the latest block in the chain ( - optional, default `latest`) -* `timeout` **number** the timeout in MS for the search (optional, default `15000`) - -Returns **Promise** - -## Request Helpers - -### newRequest - -Creates a new JSON-RPC 2.0 request - -#### Parameters - -* `method` **string** the requested method -* `params` **Array?** the requested params, if any - -Returns **RPCRequest** - -### newResponse - -Creates a new JSON-RPC 2.0 response - -#### Parameters - -* `result` **Result** the response result, if any -* `error` **RPCError** the response error, if any - -Returns **RPCResponse** - -### parseABCI - -Parses the base64 encoded ABCI JSON into a concrete type - -#### Parameters - -* `data` **string** the base64-encoded JSON - -Returns **Result** - -### stringToBase64 - -Converts a string into base64 representation - -#### Parameters - -* `str` **string** the raw string - -Returns **string** - -### base64ToUint8Array - -Converts a base64 string into a Uint8Array representation - -#### Parameters - -* `str` **string** the base64-encoded string - -Returns **Uint8Array** - -### uint8ArrayToBase64 - -Converts a Uint8Array into base64 representation - -#### Parameters - -* `data` **Uint8Array** the Uint8Array to be encoded - -Returns **string** diff --git a/docs/reference/tm2-js-client/Provider/ws-provider.md b/docs/reference/tm2-js-client/Provider/ws-provider.md deleted file mode 100644 index 9d4f2390c28..00000000000 --- a/docs/reference/tm2-js-client/Provider/ws-provider.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -id: tm2-js-ws-provider ---- - -# WebSocket Provider - -Provider based on WS JSON-RPC requests. - -### new WSProvider - -Creates a new instance of the WebSocket Provider - -#### Parameters - -* `baseURL` **string** the WS URL of the node -* `requestTimeout` **number** the timeout for the WS request (in MS) - -#### Usage - -```ts -new WSProvider('ws://staging.gno.land:26657/ws'); -// provider with WS connection is created -``` - -### closeConnection - -Closes the WS connection. Required when done working -with the WS provider - -#### Usage - -```ts -const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); - -wsProvider.closeConnection(); -// WS connection is now closed -``` - -### sendRequest - -Sends a request to the WS connection, and resolves -upon receiving the response - -#### Parameters - -* `request` **RPCRequest** the RPC request - -Returns **Promise>** - -#### Usage - -```ts -const request: RPCRequest = // ... - -const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); - -wsProvider.sendRequest(request); -// request is sent over the open WS connection -``` - -### parseResponse - -Parses the result from the response - -#### Parameters - -* `response` **RPCResponse** the response to be parsed - -Returns **Result** - -#### Usage - -```ts -const response: RPCResponse = // ... - -const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); - -wsProvider.parseResponse(response); -// response is parsed -``` - -### waitForOpenConnection - -Waits for the WS connection to be established - -Returns **Promise** - -#### Usage - -```ts -const wsProvider = new WSProvider('ws://staging.gno.land:26657/ws'); - -await wsProvider.waitForOpenConnection() -// status of the connection is: CONNECTED -``` diff --git a/docs/reference/tm2-js-client/Signer/key.md b/docs/reference/tm2-js-client/Signer/key.md deleted file mode 100644 index 3c40fa427d2..00000000000 --- a/docs/reference/tm2-js-client/Signer/key.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -id: tm2-js-key ---- - -# Key Signer - -Private key-based signer instance - -### new KeySigner - -Creates a new instance of the private-key KeySigner - -#### Parameters - -* `privateKey` **Uint8Array** the raw Secp256k1 private key -* `publicKey` **Uint8Array** the raw Secp256k1 public key -* `addressPrefix` **string** the address prefix - -#### Usage - -```ts -// Generate the public / private key from somewhere -const {publicKey, privateKey} = await generateKeyPair( - entropyToMnemonic(generateEntropy()), - index ? index : 0 -); - -new KeySigner(privateKey, publicKey); -// new Secp256k1 key signer created -``` diff --git a/docs/reference/tm2-js-client/Signer/ledger.md b/docs/reference/tm2-js-client/Signer/ledger.md deleted file mode 100644 index 71a7682c3e3..00000000000 --- a/docs/reference/tm2-js-client/Signer/ledger.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -id: tm2-js-ledger ---- - -# Ledger Signer - -Ledger device-based signer instance - -### new LedgerSigner - -Creates a new instance of the Ledger device signer, using the provided `LedgerConnector` - -#### Parameters - -* `connector` **LedgerConnector** the Ledger connector -* `accountIndex` **number** the desired account index -* `addressPrefix` **string** the address prefix - -#### Usage - -```ts -const accountIndex: number = 10 // for ex. 10th account in the derivation -const connector: LedgerConnector = // ... - -new LedgerSigner(connector, accountIndex); -// new Ledger device signer created -``` diff --git a/docs/reference/tm2-js-client/Signer/signer.md b/docs/reference/tm2-js-client/Signer/signer.md deleted file mode 100644 index fd3945cc2ae..00000000000 --- a/docs/reference/tm2-js-client/Signer/signer.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -id: tm2-js-signer ---- - -# Overview - -A `Signer` is an interface that abstracts the interaction with a single Secp256k1 key pair. It exposes methods for -signing data, verifying signatures, and getting metadata associated with the key pair, such as the address. - -Currently, the `tm2-js-client` package provides support for two `Signer` implementations: - -- [Key](key.md): a signer that is based on a raw Secp256k1 key pair. -- [Ledger](ledger.md): a signer that is based on a Ledger device, with all interaction flowing through the user's - device. - -## API - -### getAddress - -Returns the address associated with the signer's public key - -Returns **Promise** - -#### Usage - -```ts -await signer.getAddress(); -// "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" -``` - -### getPublicKey - -Returns the signer's Secp256k1-compressed public key - -Returns **Promise** - -#### Usage - -```ts -await signer.getPublicKey(); -// -``` - -### getPrivateKey - -Returns the signer's actual raw private key - -Returns **Promise** - -#### Usage - -```ts -await signer.getPrivateKey(); -// -``` - -### signData - -Generates a data signature for arbitrary input - -#### Parameters - -* `data` **Uint8Array** the data to be signed - -Returns **Promise** - -#### Usage - -```ts -const dataToSign: Uint8Array = // ... - - await signer.signData(dataToSign); -// -``` - -### verifySignature - -Verifies if the signature matches the provided raw data - -#### Parameters - -* `data` **Uint8Array** the raw data (not-hashed) -* `signature` **Uint8Array** the hashed-data signature - -Returns **Promise** - -#### Usage - -```ts -const signedData: Uint8Array = // ... -const rawData: Uint8Array = // ... - - await signer.verifySignature(rawData, signedData); -// -``` - diff --git a/docs/reference/tm2-js-client/tm2-js-client.md b/docs/reference/tm2-js-client/tm2-js-client.md deleted file mode 100644 index f32438f4113..00000000000 --- a/docs/reference/tm2-js-client/tm2-js-client.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -id: tm2-js-client ---- - -# Tendermint2 JavaScript Client - -[@gnolang/tm2-js-client](https://github.com/gnolang/tm2-js-client) is a JavaScript/TypeScript client implementation -for Tendermint2-based chains. It is designed to make it -easy for developers to interact with TM2 chains, providing a simplified API for -account and transaction management. By doing all the heavy lifting behind the -scenes, `@gnolang/tm2-js-client` enables developers to focus on what really -matters - building their dApps. - -## Key Features - -- JSON-RPC and WebSocket client support via a `Provider` -- Simple account and transaction management API with a `Wallet` -- Designed for easy extension for custom TM2 chains, such as [Gnoland](https://gno.land) - -## Installation - -To install `@gnolang/tm2-js-client`, use your preferred package manager: - -```bash -yarn add @gnolang/tm2-js-client -``` - -```bash -npm install @gnolang/tm2-js-client -``` - -## Common Terminology - -### Provider - -A `Provider` is an interface that abstracts the interaction with the Tendermint2 -chain, making it easier for users to communicate with it. Rather than requiring -users to understand which endpoints are exposed, what their return types are, -and how they are parsed, the `Provider` abstraction handles all of this behind -the scenes. It exposes useful API methods that users can use and expects -concrete types in return. - -Currently, the `@gnolang/tm2-js-client` package provides support for two -Provider implementations: - -- `JSON-RPC Provider`: executes each call as a separate HTTP RPC call. -- `WS Provider`: executes each call through an active WebSocket connection, -which requires closing when not needed anymore. - -### Signer - -A `Signer` is an interface that abstracts the interaction with a single -Secp256k1 key pair. It exposes methods for signing data, verifying signatures, -and getting metadata associated with the key pair, such as the address. - -Currently, the `@gnolang/tm2-js-client` package provides support for two -`Signer` implementations: - -- `Key`: a signer that is based on a raw Secp256k1 key pair. -- `Ledger`: a signer that is based on a Ledger device, with all interaction -flowing through the user's device. - -### Wallet - -A `Wallet` is a user-facing API that is used to interact with an account. -A `Wallet` instance is tied to a single key pair and essentially wraps the given -`Provider` for that specific account. - -A wallet can be generated from a randomly generated seed, a private key, or -instantiated using a Ledger device. - -Using the `Wallet`, users can easily interact with the Tendermint2 chain using -their account without having to worry about account management. diff --git a/docs/reference/tm2-js-client/wallet.md b/docs/reference/tm2-js-client/wallet.md deleted file mode 100644 index 8a6943dfc9a..00000000000 --- a/docs/reference/tm2-js-client/wallet.md +++ /dev/null @@ -1,275 +0,0 @@ ---- -id: tm2-js-wallet ---- - -# Wallet - -A `Wallet` is a user-facing API that is used to interact with an account. A `Wallet` instance is tied to a single key -pair and essentially wraps the given `Provider` for that specific account. - -A wallet can be generated from a randomly generated seed, a private key, or instantiated using a Ledger device. - -Using the `Wallet`, users can easily interact with the Tendermint2 chain using their account without having to worry -about account management. - -## Initialization - -### createRandom - -Generates a private key-based wallet, using a random seed - -#### Parameters - -* `options?` **AccountWalletOption** the account options - -Returns **Promise** - -#### Usage - -```ts -const wallet: Wallet = await Wallet.createRandom(); -// random wallet created -``` - -### fromMnemonic - -Generates a bip39 mnemonic-based wallet - -#### Parameters - -* `mnemonic` **string** the bip39 mnemonic -* `options?` **CreateWalletOptions** the wallet generation options - -Returns **Promise** - -#### Usage - -```ts -const mnemonic: string = // ... -const wallet: Wallet = await Wallet.fromMnemonic(mnemonic); -// wallet created from mnemonic -``` - -### fromPrivateKey - -Generates a private key-based wallet - -#### Parameters - -* `privateKey` **string** the private key -* `options?` **AccountWalletOption** the wallet generation options - -Returns **Promise** - -#### Usage - -```ts -// Generate the private key from somewhere -const {publicKey, privateKey} = await generateKeyPair( - entropyToMnemonic(generateEntropy()), - index ? index : 0 -); - -const wallet: Wallet = await Wallet.fromPrivateKey(privateKey); -// wallet created from private key -``` - -### fromLedger - -Creates a Ledger-based wallet - -#### Parameters - -* `connector` **LedgerConnector** the Ledger device connector -* `options?` **CreateWalletOptions** the wallet generation options - -Returns **Wallet** - -#### Usage - -```ts -const connector: LedgerConnector = // ... - -const wallet: Wallet = await Wallet.fromLedger(connector); -// wallet created from Ledger device connection -``` - -## Provider Methods - -### connect - -Connects the wallet to the specified Provider - -#### Parameters - -* `provider` **Provider** the active Provider, if any - -#### Usage - -```ts -const provider: Provider = // ... - - wallet.connect(provider); -// Provider connected to Wallet -``` - -### getProvider - -Returns the connected provider, if any - -Returns **Provider** - -#### Usage - -```ts -wallet.getProvider(); -// connected provider, if any (undefined if not) -``` - -## Account Methods - -### getAddress - -Fetches the address associated with the wallet - -Returns **Promise** - -#### Usage - -```ts -await wallet.getAddress(); -// "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" -``` - -### getSequence - -Fetches the account sequence for the wallet - -#### Parameters - -* `height` **number** the block height (optional, default `latest`) - -#### Usage - -```ts -await wallet.getSequence(); -// 42 -``` - -Returns **Promise** - -### getAccountNumber - -Fetches the account number for the wallet. Errors out if the -account is not initialized - -#### Parameters - -* `height` **number** the block height (optional, default `latest`) - -Returns **Promise** - -#### Usage - -```ts -await wallet.getAccountNumber(); -// 10 -``` - -### getBalance - -Fetches the account balance for the specific denomination - -#### Parameters - -* `denomination` **string** the fund denomination (optional, default `ugnot`) - -Returns **Promise** - -#### Usage - -```ts -await wallet.getBalance('ugnot'); -// 5000 -``` - -### getGasPrice - -Fetches the current (recommended) average gas price - -Returns **Promise** - -#### Usage - -```ts -await wallet.getGasPrice(); -// 63000 -``` - -### estimateGas - -Estimates the gas limit for the transaction - -#### Parameters - -* `tx` **Tx** the transaction that needs estimating - -Returns **Promise** - -#### Usage - -```ts -const tx: Tx = // ... - - await wallet.estimateGas(tx); -// 120000 -``` - -### signTransaction - -Generates a transaction signature, and appends it to the transaction - -#### Parameters - -* `tx` **Tx** the transaction to be signed - -Returns **Promise** - -#### Usage - -```ts -const tx: Tx = // ... - - await wallet.signTransaction(tx); -// transaction with appended signature -``` - -### sendTransaction - -Signs and sends the transaction. Returns the transaction hash (base-64) - -#### Parameters - -* `tx` **Tx** the unsigned transaction - -Returns **Promise** - -#### Usage - -```ts -await wallet.sendTransaction(tx); -// returns the transaction hash -``` - -### getSigner - -Returns the associated signer - -Returns **Signer** - -#### Usage - -```ts -wallet.getSigner(tx); -// Signer instance -``` diff --git a/docs/concepts/effective-gno.md b/docs/resources/effective-gno.md similarity index 80% rename from docs/concepts/effective-gno.md rename to docs/resources/effective-gno.md index 45872dd1d63..e0c12e90423 100644 --- a/docs/concepts/effective-gno.md +++ b/docs/resources/effective-gno.md @@ -1,7 +1,3 @@ ---- -id: effective-gno ---- - # Effective Gno Welcome to the guide for writing effective Gno code. This document is designed @@ -59,12 +55,12 @@ var counter int // public getter endpoint. func GetCounter() int { - return counter + return counter } // public setter endpoint. func IncCounter() { - counter++ + counter++ } ``` @@ -82,7 +78,7 @@ Go developers "Don't panic.", in Gno, we actually embrace `panic`. Panic in Gno is not just for critical errors or programming mistakes as it is in Go. Instead, it's used as a control flow mechanism to stop the execution of a -[realm](realms.md) when something goes wrong. This could be due to an invalid input, a +[realm](./realms.md) when something goes wrong. This could be due to an invalid input, a failed precondition, or any other situation where it's not possible or desirable to continue executing the contract. @@ -102,7 +98,7 @@ everything and not save wrong changes. In Gno, the use of `panic()` and `error` should be context-dependent to ensure clarity and proper error handling: - Use `panic()` to immediately halt execution and roll back the transaction when - encountering critical issues or invalid inputs that cannot be recovered from. + encountering critical issues or invalid inputs that cannot be recovered from. - Return an `error` when the situation allows for the possibility of recovery or when the caller should decide how to handle the error. @@ -115,11 +111,11 @@ that could lead to user frustration or the need to fork the code. import "std" func Foobar() { - caller := std.PrevRealm().Addr() - if caller != "g1xxxxx" { - panic("permission denied") - } - // ... + caller := std.PreviousRealm().Address() + if caller != "g1xxxxx" { + panic("permission denied") + } + // ... } ``` @@ -138,15 +134,15 @@ you start a program, in Gno, `init()` is executed once in a realm's lifetime. In Gno, `init()` primarily serves two purposes: 1. It establishes the initial state, specifically, setting up global variables. - - Note: global variables can often be set up just by assigning their initial value when you're declaring them. See below for an example! \ - Deciding when to initialise the variable directly, and when to set it up in `init` can be non-straightforward. As a rule of thumb, though, `init` visually marks the code as executing only when the realm is started, while assigning the variables can be less straightforward. + - Note: global variables can often be set up just by assigning their initial value when you're declaring them. See below for an example! \ + Deciding when to initialise the variable directly, and when to set it up in `init` can be non-straightforward. As a rule of thumb, though, `init` visually marks the code as executing only when the realm is started, while assigning the variables can be less straightforward. 2. It communicates with another realm, for example, to register itself in a registry. ```go import "gno.land/r/some/registry" func init() { - registry.Register("myID", myCallback) + registry.Register("myID", myCallback) } func myCallback(a, b string) { /* ... */ } @@ -157,25 +153,25 @@ package. ```go import ( - "std" - "time" + "std" + "time" ) var ( - created time.Time - admin std.Address - list = []string{"foo", "bar", time.Now().Format("15:04:05")} + created time.Time + admin std.Address + list = []string{"foo", "bar", time.Now().Format("15:04:05")} ) func init() { - created = time.Now() - // std.GetOrigCaller in the context of realm initialisation is, - // of course, the publisher of the realm :) - // This can be better than hardcoding an admin address as a constant. - admin = std.GetOrigCaller() - // list is already initialized, so it will already contain "foo", "bar" and - // the current time as existing items. - list = append(list, admin.String()) + created = time.Now() + // std.OriginCaller in the context of realm initialisation is, + // of course, the publisher of the realm :) + // This can be better than hardcoding an admin address as a constant. + admin = std.OriginCaller() + // list is already initialized, so it will already contain "foo", "bar" and + // the current time as existing items. + list = append(list, admin.String()) } ``` @@ -199,7 +195,7 @@ code readability and trust. A Gno contract is not just its lines of code, but also the imports it uses. More importantly, Gno contracts are not just for developers. For the first time, it -makes sense for users to see what functionality they are executing too. Code simplicity, transparency, +makes sense for users to see what functionality they are executing too. Code simplicity, transparency, explicitness, and trustability are paramount. Another good reason for creating simple, focused libraries is the composability @@ -237,36 +233,36 @@ efficient, and trustworthy Gno contracts. ```go import ( - "gno.land/p/finance/tokens" - "gno.land/p/finance/exchange" - "gno.land/p/finance/wallet" - "gno.land/p/utils/permissions" + "gno.land/p/finance/tokens" + "gno.land/p/finance/exchange" + "gno.land/p/finance/wallet" + "gno.land/p/utils/permissions" ) var ( - myWallet wallet.Wallet - myToken tokens.Token - myExchange exchange.Exchange + myWallet wallet.Wallet + myToken tokens.Token + myExchange exchange.Exchange ) func init() { - myWallet = wallet.NewWallet() - myToken = tokens.NewToken("MyToken", "MTK") - myExchange = exchange.NewExchange(myToken) + myWallet = wallet.NewWallet() + myToken = tokens.NewToken("MyToken", "MTK") + myExchange = exchange.NewExchange(myToken) } func BuyTokens(amount int) { - caller := permissions.GetCaller() - permissions.CheckPermission(caller, "buy") - myWallet.Debit(caller, amount) - myExchange.Buy(caller, amount) + caller := permissions.GetCaller() + permissions.CheckPermission(caller, "buy") + myWallet.Debit(caller, amount) + myExchange.Buy(caller, amount) } func SellTokens(amount int) { - caller := permissions.GetCaller() - permissions.CheckPermission(caller, "sell") - myWallet.Credit(caller, amount) - myExchange.Sell(caller, amount) + caller := permissions.GetCaller() + permissions.CheckPermission(caller, "sell") + myWallet.Credit(caller, amount) + myExchange.Sell(caller, amount) } ``` @@ -297,7 +293,57 @@ main purpose in Gno is for discoverability. This shift towards user-centric documentation reflects the broader shift in Gno towards making code more accessible and understandable for all users, not just developers. -TODO: `func ExampleXXX`. +Here's an example from [grc20](https://gno.land/p/demo/grc/grc20$source&file=types.gno) +to illustrate the concept: + +```go +// Teller interface defines the methods that a GRC20 token must implement. It +// extends the TokenMetadata interface to include methods for managing token +// transfers, allowances, and querying balances. +// +// The Teller interface is designed to ensure that any token adhering to this +// standard provides a consistent API for interacting with fungible tokens. +type Teller interface { + exts.TokenMetadata + + // Returns the amount of tokens in existence. + TotalSupply() uint64 + + // Returns the amount of tokens owned by `account`. + BalanceOf(account std.Address) uint64 + + // Moves `amount` tokens from the caller's account to `to`. + // + // Returns an error if the operation failed. + Transfer(to std.Address, amount uint64) error + + // Returns the remaining number of tokens that `spender` will be + // allowed to spend on behalf of `owner` through {transferFrom}. This is + // zero by default. + // + // This value changes when {approve} or {transferFrom} are called. + Allowance(owner, spender std.Address) uint64 + + // Sets `amount` as the allowance of `spender` over the caller's tokens. + // + // Returns an error if the operation failed. + // + // IMPORTANT: Beware that changing an allowance with this method brings + // the risk that someone may use both the old and the new allowance by + // unfortunate transaction ordering. One possible solution to mitigate + // this race condition is to first reduce the spender's allowance to 0 + // and set the desired value afterwards: + // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + Approve(spender std.Address, amount uint64) error + + // Moves `amount` tokens from `from` to `to` using the + // allowance mechanism. `amount` is then deducted from the caller's + // allowance. + // + // Returns an error if the operation failed. + TransferFrom(from, to std.Address, amount uint64) error +} +``` ### Reflection is never clear @@ -352,10 +398,30 @@ just to have internal libraries that you created to centralize your helpers and don't expect that other people will use your helpers, then you should probably use subdirectories like `p/NAMESPACE/DAPP/foo/bar/baz`. +Packages which contain `internal` as an element of the path (ie. at the end, or +in between, like `gno.land/p/demo/seqid/internal`, or +`gno.land/p/demo/seqid/internal/base32`) can only be imported by packages +sharing the same root as the `internal` package. That is, given a package +structure as follows: + +``` +gno.land/p/demo/seqid +├── generator +└── internal + ├── base32 + └── cford32 +``` + +The `seqid/internal`, `seqid/internal/base32` and `seqid/internal/cford32` +packages can only be imported by `seqid` and `seqid/generator`. + +This works for both realm and packages, and can be used to create entirely +restricted packages and realms that are not meant for outside consumption. + ### Define types and interfaces in pure packages (p/) In Gno, it's common to create `p/NAMESPACE/DAPP` for defining types and -interfaces, and `r/NAMESPACE/DAPP` for the runtime, especially when the goal +interfaces, and `r/NAMESPACE/DAPP` for the runtime, especially when the goal for the realm is to become a standard that could be imported by `p/`. The reason for this is that `p/` can only import `p/`, while `r/` can import @@ -399,15 +465,15 @@ certain operations. import "std" func PublicMethod(nb int) { - caller := std.PrevRealm().Addr() - privateMethod(caller, nb) + caller := std.PreviousRealm().Address() + privateMethod(caller, nb) } func privateMethod(caller std.Address, nb int) { /* ... */ } ``` In this example, `PublicMethod` is a public function that can be called by other -realms. It retrieves the caller's address using `std.PrevRealm().Addr()`, and +realms. It retrieves the caller's address using `std.PreviousRealm().Address()`, and then passes it to `privateMethod`, which is a private function that performs the actual logic. This way, `privateMethod` can only be called from within the realm, and it can use the caller's address for authentication or authorization @@ -415,19 +481,19 @@ checks. ### Emit Gno events to make life off-chain easier -Gno provides users the ability to log specific occurrences that happened in their +Gno provides users the ability to log specific occurrences that happened in their on-chain apps. An `event` log is stored in the ABCI results of each block, and -these logs can be indexed, filtered, and searched by external services, allowing +these logs can be indexed, filtered, and searched by external services, allowing them to monitor the behaviour of on-chain apps. -It is good practice to emit events when any major action in your code is +It is good practice to emit events when any major action in your code is triggered. For example, good times to emit an event is after a balance transfer, ownership change, profile created, etc. Alternatively, you can view event emission -as a way to include data for monitoring purposes, given the indexable nature of +as a way to include data for monitoring purposes, given the indexable nature of events. Events consist of a type and a slice of strings representing `key:value` pairs. -They are emitted with the `Emit()` function, contained in the `std` package in +They are emitted with the `Emit()` function, contained in the `std` package in the Gno standard library: ```go @@ -440,16 +506,16 @@ import ( var owner std.Address func init() { - owner = std.PrevRealm().Addr() + owner = std.PreviousRealm().Address() } func ChangeOwner(newOwner std.Address) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if caller != owner { panic("access denied") } - + owner = newOwner std.Emit("OwnershipChange", "newOwner", newOwner.String()) } @@ -478,7 +544,7 @@ of block #43 will contain the following data: } ``` -Read more about events [here](./stdlibs/events.md). +Read more about events [here](./gno-stdlibs.md#events). ### Contract-level access control @@ -494,14 +560,14 @@ whitelisted or not. Let's deep dive into the different access control mechanisms we can use: -One strategy is to look at the caller with `std.PrevRealm()`, which could be the +One strategy is to look at the caller with `std.PreviousRealm()`, which could be the EOA (Externally Owned Account), or the preceding realm in the call stack. Another approach is to look specifically at the EOA. For this, you should call -`std.GetOrigCaller()`, which returns the public address of the account that +`std.OriginCaller()`, which returns the public address of the account that signed the transaction. -TODO: explain when to use `std.GetOrigCaller`. +TODO: explain when to use `std.OriginCaller`. Internally, this call will look at the frame stack, which is basically the stack of callers including all the functions, anonymous functions, other realms, and @@ -516,18 +582,18 @@ import "std" var admin std.Address = "g1xxxxx" func AdminOnlyFunction() { - caller := std.PrevRealm().Addr() - if caller != admin { - panic("permission denied") - } - // ... + caller := std.PreviousRealm().Address() + if caller != admin { + panic("permission denied") + } + // ... } // func UpdateAdminAddress(newAddr std.Address) { /* ... */ } ``` In this example, `AdminOnlyFunction` is a function that can only be called by -the admin. It retrieves the caller's address using `std.PrevRealm().Addr()`, +the admin. It retrieves the caller's address using `std.PreviousRealm().Address()`, this can be either another realm contract, or the calling user if there is no other intermediary realm. and then checks if the caller is the admin. If not, it panics and stops the execution. @@ -543,16 +609,16 @@ Here's an example: import "std" func TransferTokens(to std.Address, amount int64) { - caller := std.PrevRealm().Addr() - if caller != admin { - panic("permission denied") - } - // ... + caller := std.PreviousRealm().Address() + if caller != admin { + panic("permission denied") + } + // ... } ``` In this example, `TransferTokens` is a function that can only be called by the -admin. It retrieves the caller's address using `std.PrevRealm().Addr()`, and +admin. It retrieves the caller's address using `std.PreviousRealm().Address()`, and then checks if the caller is the admin. If not, the function panics and execution is stopped. By using these access control mechanisms, you can ensure that your contract's @@ -584,11 +650,11 @@ import "avl" var tree avl.Tree func GetPost(id string) *Post { - return tree.Get(id).(*Post) + return tree.Get(id).(*Post) } func AddPost(id string, post *Post) { - tree.Set(id, post) + tree.Set(id, post) } ``` @@ -626,37 +692,37 @@ usage completely. ```go type MySafeStruct { - counter nb - admin std.Address + counter nb + admin std.Address } func NewSafeStruct() *MySafeStruct { - caller := std.PrevRealm().Addr() - return &MySafeStruct{ - counter: 0, - admin: caller, - } + caller := std.PreviousRealm().Address() + return &MySafeStruct{ + counter: 0, + admin: caller, + } } func (s *MySafeStruct) Counter() int { return s.counter } func (s *MySafeStruct) Inc() { - caller := std.PrevRealm().Addr() - if caller != s.admin { - panic("permission denied") - } - s.counter++ + caller := std.PreviousRealm().Address() + if caller != s.admin { + panic("permission denied") + } + s.counter++ } ``` Then, you can register this object in another or several other realms so other realms can access the object, but still following your own rules. -```go +```go import "gno.land/r/otherrealm" func init() { - mySafeObj := NewSafeStruct() - otherrealm.Register(mySafeObject) + mySafeObj := NewSafeStruct() + otherrealm.Register(mySafeObject) } // then, other realm can call the public functions but won't be the "owner" of @@ -679,7 +745,7 @@ For example, if you're creating a coin for cross-chain transfers, Coins are your best bet. They're IBC-ready and their strict rules offer top-notch security. -Read about how to use the Banker module [here](stdlibs/banker.md). +Read about how to use the Banker module [here](./gno-stdlibs.md#banker). #### GRC20 tokens @@ -704,7 +770,7 @@ import "gno.land/p/demo/grc/grc20" var fooToken = grc20.NewBanker("Foo Token", "FOO", 4) func MyBalance() uint64 { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() return fooToken.BalanceOf(caller) } ``` @@ -722,6 +788,7 @@ See also: https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/wu + +This provides transparency and allows you to learn from existing code. + +## Building Your Own Packages + +For detailed instructions on creating your own packages: + +- For realms, see [Example Minisocial dApp](../builders/example-minisocial-dapp.md) +- For deployment, see [Deploying Gno Packages](../builders/deploy-packages.md) diff --git a/docs/resources/gno-stdlibs.md b/docs/resources/gno-stdlibs.md new file mode 100644 index 00000000000..d6db931179a --- /dev/null +++ b/docs/resources/gno-stdlibs.md @@ -0,0 +1,920 @@ +# Standard Libraries + +Gno comes with a set of standard libraries which are included to ease development +and provide extended functionality to the language. These include: +- standard libraries as we know them in classic Go, i.e. `strings`, `testing`, etc. +- a special `std` package, which contains types, interfaces, and APIs created to +handle blockchain-related functionality, such as fetching the last caller, +fetching coins sent along with a transaction, getting the block timestamp and height, and more. + +Standard libraries differ from on-chain packages in terms of their import path structure. +Unlike on-chain [packages](./gno-packages.md), standard libraries do not incorporate +a domain-like format at the beginning of their import path. For example: +- `import "strings"` refers to a standard library +- `import "gno.land/p/demo/avl"` refers to an on-chain package. + +To see concrete implementation details & API references of the `std` pacakge, +see the reference section. + +## Accessing documentation + +Apart from the official documentation you are currently reading, you can also +access documentation for the standard libraries in several other different ways. +You can obtain a list of all the available standard libraries 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 +... +``` + +All the packages have automatically generated documentation through the use of the +`gno doc` command, which has similar functionality and features to `go doc`: + +```console +$ gno doc encoding/binary +package binary // import "encoding/binary" + +Package binary implements simple translation between numbers and byte sequences +and encoding and decoding of varints. + +[...] + +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" + +func (littleEndian) AppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} +``` + +`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`. + +Another alternative is setting your environment variable `GNOROOT` to point to +where you cloned the Gno repository. + +```sh +export GNOROOT=$HOME/gno +``` + +## Coin + +A Coin is a native Gno type that has a denomination and an amount. Coins can be +issued by the native Gno Banker. + +A coin is defined by the following: + +```go +type Coin struct { + Denom string `json:"denom"` + Amount int64 `json:"amount"` +} +``` + +`Denom` is the denomination of the coin, i.e. `ugnot`, and `Amount` is a +non-negative amount of the coin. + +Multiple coins can be bundled together into a `Coins` slice: + +```go +type Coins []Coin +``` + +This slice behaves like a mathematical set - it cannot contain duplicate `Coin` instances. + +The `Coins` slice can be included in a transaction made by a user addresses or a realm. +Coins in this set are then available for access by specific types of Bankers, +which can manipulate them depending on access rights. + +Read more about coins in the [Effective Gno](./effective-gno.md) section. + +The Coin(s) API can be found in the `std` package. + +## Banker + +The Banker's main purpose is to handle balance changes of [native coins](coin.md) +within Gno chains. This includes issuance, transfers, and burning of coins. + +The Banker module can be cast into 4 subtypes of bankers that expose different +functionalities and safety features within your packages and realms. + +### Banker Types + +1. `BankerTypeReadonly` - read-only access to coin balances +2. `BankerTypeOriginSend` - full access to coins sent with the transaction that called the banker +3. `BankerTypeRealmSend` - full access to coins that the realm itself owns, including the ones sent with the transaction +4. `BankerTypeRealmIssue` - able to issue new coins + +## Events + +Events in Gno are a fundamental aspect of interacting with and monitoring +on-chain applications. They serve as a bridge between the on-chain environment +and off-chain services, making it simpler for developers, analytics tools, and +monitoring services to track and respond to activities happening in gno.land. + +Gno events are pieces of data that log specific activities or changes occurring +within the state of an on-chain app. These activities are user-defined; they might +be token transfers, changes in ownership, updates in user profiles, and more. +Each event is recorded in the ABCI results of each block, ensuring that action +that happened is verifiable and accessible to off-chain services. + +To emit an event, you can use the `Emit()` function from the `std` package +provided in the Gno standard library. The `Emit()` function takes in a string +representing the type of event, and an even number of arguments after representing +`key:value` pairs. + +Read more about events & `Emit()` in +[Effective Gno](./effective-gno.md#emit-gno-events-to-make-life-off-chain-easier). + +An event contained in an ABCI response of a block will include the following +data: + +``` json +{ + "@type": "/tm.gnoEvent", // TM2 type + "type": "OwnershipChange", // Type/name of event defined in Gno + "pkg_path": "gno.land/r/demo/example", // Path of the emitter + "func": "ChangeOwner", // Gno function that emitted the event + "attrs": [ // Slice of key:value pairs emitted + { + "key": "oldOwner", + "value": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + }, + { + "key": "newOwner", + "value": "g1zzqd6phlfx0a809vhmykg5c6m44ap9756s7cjj" + } + ] +} +``` + +You can fetch the ABCI response of a specific block by using the `/block_results` +RPC endpoint. + + + +## Package `std` + +[//]: <> (todo: autogenerate from godoc.) + +This is the reference page for the special `std` package found in Gno, containing +critical functionality for managing realms, addresses, the Banker module, etc. + +## Address +Native address type in Gno, conforming to the Bech32 format. + +```go +type Address string +func (a Address) IsValid() bool {...} +func (a Address) String() string {...} +``` + +### IsValid +Check if **Address** is of a valid length, and conforms to the bech32 format. + +##### Usage +```go +if !address.IsValid() {...} +``` + +--- + +### String +Get **string** representation of **Address**. + +##### Usage +```go +stringAddr := addr.String() +``` + +--- + +## Banker + +```go +type BankerType uint8 + +const ( + BankerTypeReadonly BankerType = iota + BankerTypeOriginSend + BankerTypeRealmSend + BankerTypeRealmIssue +) + +type Banker interface { + GetCoins(addr Address) (dst Coins) + SendCoins(from, to Address, coins Coins) + IssueCoin(addr Address, denom string, amount int64) + RemoveCoin(addr Address, denom string, amount int64) +} +``` + +### NewBanker +Returns `Banker` of the specified type. + +##### Parameters +- `BankerType` - type of Banker to get: + - `BankerTypeReadonly` - read-only access to coin balances + - `BankerTypeOrigSend` - full access to coins sent with the transaction that calls the banker + - `BankerTypeRealmSend` - full access to coins that the realm itself owns, including the ones sent with the transaction + - `BankerTypeRealmIssue` - able to issue new coins + +##### Usage + +```go +banker := std.NewBanker(std.) +``` +--- + +### GetCoins +Returns `Coins` owned by `Address`. + +##### Parameters +- `addr` **Address** to fetch balances for + +##### Usage + +```go +coins := banker.GetCoins(addr) +``` +--- + +### SendCoins +Sends `coins` from address `from` to address `to`. `coins` needs to be a well-defined +`Coins` slice. + +##### Parameters +- `from` **Address** to send from +- `to` **Address** to send to +- `coins` **Coins** to send + +##### Usage +```go +banker.SendCoins(from, to, coins) +``` +--- + +### IssueCoin +Issues `amount` of coin with a denomination `denom` to address `addr`. + +##### Parameters +- `addr` **Address** to issue coins to +- `denom` **string** denomination of coin to issue +- `amount` **int64** amount of coin to issue + +##### Usage +```go +banker.IssueCoin(addr, denom, amount) +``` + +:::info Coin denominations + +`Banker` methods expect qualified denomination of the coins. Read more [here](#coindenom). + +::: + +--- + + +### RemoveCoin +Removes (burns) `amount` of coin with a denomination `denom` from address `addr`. + +##### Parameters +- `addr` **Address** to remove coins from +- `denom` **string** denomination of coin to remove +- `amount` **int64** amount of coin to remove + +##### Usage +```go +banker.RemoveCoin(addr, denom, amount) +``` + +--- + +## Chain-related + +### AssertOriginCall +```go +func AssertOriginCall() +``` +Panics if caller of function is not an EOA. + +##### Usage +```go +std.AssertOriginCall() +``` +--- + +### ChainDomain +```go +func ChainDomain() string +``` +Returns the chain domain. Currently only `gno.land` is supported. + +##### Usage +```go +domain := std.ChainDomain() // gno.land +``` +--- + +### Emit +```go +func Emit(typ string, attrs ...string) +``` +Emits a Gno event. Takes in a **string** type (event identifier), and an even number of string +arguments acting as key-value pairs to be included in the emitted event. + +##### Usage +```go +std.Emit("MyEvent", "myKey1", "myValue1", "myKey2", "myValue2") +``` +--- + +### ChainID +```go +func ChainID() string +``` +Returns the chain ID. + +##### Usage +```go +chainID := std.ChainID() // dev | test5 | main ... +``` +--- + +### ChainHeight +```go +func ChainHeight() int64 +``` +Returns the current block number (height). + +##### Usage +```go +height := std.ChainHeight() +``` +--- + +### OriginSend +```go +func OriginSend() Coins +``` +Returns the `Coins` that were sent along with the calling transaction. + +##### Usage +```go +coinsSent := std.OriginSend() +``` +--- + +### OriginCaller +```go +func OriginCaller() Address +``` +Returns the original signer of the transaction. + +##### Usage +```go +caller := std.OriginCaller() +``` +--- + +### OriginPkgAddress +```go +func OriginPkgAddress() Address +``` +Returns the address of the first (entry point) realm/package in a sequence of realm/package calls. + +##### Usage +```go +addr := std.OriginPkgAddress() +``` +--- + +### CurrentRealm +```go +func CurrentRealm() Realm +``` +Returns current [Realm](./realms.md) object. + +##### Usage +```go +currentRealm := std.CurrentRealm() +``` +--- + +### PrevRealm +```go +func PreviousRealm() Realm +``` +Returns the previous caller [realm](./realms.md) (can be code or user realm). If caller is a +user realm, `pkgpath` will be empty. + +##### Usage +```go +prevRealm := std.PreviousRealm() +``` +--- + +### CallerAt +```go +func CallerAt(n int) Address +``` +Returns the n-th caller of the function, going back in the call trace. +Includes calls to pure packages. + +##### Usage +```go +currentRealm := std.CallerAt(1) // returns address of current realm +previousRealm := std.CallerAt(2) // returns address of previous realm/caller +std.CallerAt(0) // error, n must be > 0 +``` +--- + +### DerivePkgAddr +```go +func DerivePkgAddr(pkgPath string) Address +``` +Derives the Realm address from its `pkgpath` parameter. + +##### Usage +```go +realmAddr := std.DerivePkgAddr("gno.land/r/demo/tamagotchi") // g1a3tu874agjlkrpzt9x90xv3uzncapcn959yte4 +``` + +--- + +### CoinDenom +```go +func CoinDenom(pkgPath, coinName string) string +``` +Composes a qualified denomination string from the realm's `pkgPath` and the +provided coin name, e.g. `/gno.land/r/demo/blog:blgcoin`. This method should be +used to get fully qualified denominations of coins when interacting with the +`Banker` module. It can also be used as a method of the `Realm` object. +Read more[here](#coindenom-1). + +#### Parameters +- `pkgPath` **string** - package path of the realm +- `coinName` **string** - The coin name used to build the qualified denomination. Must start with a lowercase letter, followed by 2–15 lowercase letters or digits. + +#### Usage +```go +denom := std.CoinDenom("gno.land/r/demo/blog", "blgcoin") // /gno.land/r/demo/blog:blgcoin +``` + +--- + +## Coin + +```go +type Coin struct { + Denom string `json:"denom"` + Amount int64 `json:"amount"` +} + +func NewCoin(denom string, amount int64) Coin {...} +func (c Coin) String() string {...} +func (c Coin) IsGTE(other Coin) bool {...} +func (c Coin) IsLT(other Coin) bool {...} +func (c Coin) IsEqual(other Coin) bool {...} +func (c Coin) Add(other Coin) Coin {...} +func (c Coin) Sub(other Coin) Coin {...} +func (c Coin) IsPositive() bool {...} +func (c Coin) IsNegative() bool {...} +func (c Coin) IsZero() bool {...} +``` + +### NewCoin +Returns a new Coin with a specific denomination and amount. + +##### Usage +```go +coin := std.NewCoin("ugnot", 100) +``` +--- + +### String +Returns a string representation of the `Coin` it was called upon. + +##### Usage +```go +coin := std.NewCoin("ugnot", 100) +coin.String() // 100ugnot +``` +--- + +### IsGTE +Checks if the amount of `other` Coin is greater than or equal than amount of +Coin `c` it was called upon. If coins compared are not of the same denomination, +`IsGTE` will panic. + +##### Parameters +- `other` **Coin** to compare with + +##### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 100) + +coin1.IsGTE(coin2) // true +coin2.IsGTE(coin1) // false +``` +--- + +### IsLT +Checks if the amount of `other` Coin is less than the amount of Coin `c` it was +called upon. If coins compared are not of the same denomination, `IsLT` will +panic. + +##### Parameters +- `other` **Coin** to compare with + +##### Usage +```go +coin := std.NewCoin("ugnot", 150) +coin := std.NewCoin("ugnot", 100) + +coin1.IsLT(coin2) // false +coin2.IsLT(coin1) // true +``` +--- + +### IsEqual +Checks if the amount of `other` Coin is equal to the amount of Coin `c` it was +called upon. If coins compared are not of the same denomination, `IsEqual` will +panic. + +##### Parameters +- `other` **Coin** to compare with + +##### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 100) +coin3 := std.NewCoin("ugnot", 100) + +coin1.IsEqual(coin2) // false +coin2.IsEqual(coin1) // false +coin2.IsEqual(coin3) // true +``` +--- + +### Add +Adds two coins of the same denomination. If coins are not of the same +denomination, `Add` will panic. If final amount is larger than the maximum size +of `int64`, `Add` will panic with an overflow error. Adding a negative amount +will result in subtraction. + +##### Parameters +- `other` **Coin** to add + +##### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 100) + +coin3 := coin1.Add(coin2) +coin3.String() // 250ugnot +``` +--- + +### Sub +Subtracts two coins of the same denomination. If coins are not of the same +denomination, `Sub` will panic. If final amount is smaller than the minimum size +of `int64`, `Sub` will panic with an underflow error. Subtracting a negative amount +will result in addition. + +##### Parameters +- `other` **Coin** to subtract + +##### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 100) + +coin3 := coin1.Sub(coin2) +coin3.String() // 50ugnot +``` +--- + +### IsPositive +Checks if a coin amount is positive. + +##### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", -150) + +coin1.IsPositive() // true +coin2.IsPositive() // false +``` +--- + +### IsNegative +Checks if a coin amount is negative. + +##### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", -150) + +coin1.IsNegative() // false +coin2.IsNegative() // true +``` +--- + +### IsZero +Checks if a coin amount is zero. + +##### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("ugnot", 0) + +coin1.IsZero() // false +coin2.IsZero() // true +``` + +--- + +## Coins + +`Coins` is a set of `Coin`, one per denomination. + +```go +type Coins []Coin + +func NewCoins(coins ...Coin) Coins {...} +func (c Coins) String() string {...} +func (c Coins) AmountOf(denom string) int64 {...} +func (c Coins) Add(other Coins) Coins {...} +``` + +### NewCoins +Returns a new set of `Coins` given one or more `Coin`. Consolidates any denom +duplicates into one, keeping the properties of a mathematical set. + +##### Usage +```go +coin1 := std.NewCoin("ugnot", 150) +coin2 := std.NewCoin("example", 100) +coin3 := std.NewCoin("ugnot", 100) + +coins := std.NewCoins(coin1, coin2, coin3) +coins.String() // 250ugnot, 100example +``` +--- + +#### String +Returns a string representation of the `Coins` set it was called upon. + +##### Usage +```go +coins := std.Coins{std.Coin{"ugnot", 100}, std.Coin{"foo", 150}, std.Coin{"bar", 200}} +coins.String() // 100ugnot,150foo,200bar +``` +--- + +### AmountOf +Returns **int64** amount of specified coin within the `Coins` set it was called upon. Returns `0` if the specified coin does not exist in the set. + +#### Parameters +- `denom` **string** denomination of specified coin + +#### Usage +```go +coins := std.Coins{std.Coin{"ugnot", 100}, std.Coin{"foo", 150}, std.Coin{"bar", 200}} +coins.AmountOf("foo") // 150 +``` +--- + +### Add +Adds (or updates) the amount of specified coins in the `Coins` set. If the specified coin does not exist, `Add` adds it to the set. + +#### Parameters +- `other` **Coins** to add to `Coins` set + +#### Usage +```go +coins := // ... +otherCoins := // ... +coins.Add(otherCoins) +``` + +## Realm + +`Realm` is the structure representing a realm in Gno. See our [realm documentation](./realms.md) for more details. + +```go +type Realm struct { + addr Address + pkgPath string +} + +func (r Realm) Address() Address {...} +func (r Realm) PkgPath() string {...} +func (r Realm) IsUser() bool {...} +func (r Realm) CoinDenom(coinName string) string {...} +``` + +### Addr +Returns the **Address** field of the realm it was called upon. + +##### Usage +```go +realmAddr := r.Address() // eg. g1n2j0gdyv45aem9p0qsfk5d2gqjupv5z536na3d +``` +--- +### PkgPath +Returns the **string** package path of the realm it was called upon. + +##### Usage +```go +realmPath := r.PkgPath() // eg. gno.land/r/gnoland/blog +``` +--- +### IsUser +Checks if the realm it was called upon is a user realm. + +##### Usage +```go +if r.IsUser() {...} +``` + +--- + +### CoinDenom + +Composes a qualified denomination string from the realm's `pkgPath` and the +provided coin name, e.g. `/gno.land/r/demo/blog:blgcoin`. This method should be +used to get fully qualified denominations of coins when interacting with the +`Banker` module. + +#### Parameters +- `coinName` **string** - The coin name used to build the qualified denomination. +Must start with a lowercase letter, followed by 2–15 lowercase letters or digits. + +#### Usage +```go +// in "gno.land/r/gnoland/blog" +denom := r.CoinDenom("blgcoin") // /gno.land/r/gnoland/blog:blgcoin +``` + +--- + +## Testing + +```go +func TestSkipHeights(count int64) +func TestSetOriginCaller(addr Address) +func TestSetOriginPkgAddress(addr Address) +func TestSetOriginSend(sent, spent Coins) +func TestIssueCoins(addr Address, coins Coins) +func TestSetRealm(realm Realm) +func NewUserRealm(address Address) Realm +func NewCodeRealm(pkgPath string) Realm +``` + +### TestSkipHeights + +```go +func TestSkipHeights(count int64) +``` +Modifies the block height variable by skipping **count** blocks. + +It also increases block timestamp by 5 seconds for every single count + +#### Usage +```go +std.TestSkipHeights(100) +``` +--- + +### TestSetOriginCaller + +```go +func TestSetOriginCaller(addr Address) +``` +Sets the current caller of the transaction to **addr**. + +#### Usage +```go +std.TestSetOriginCaller(std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")) +``` +--- + +### TestSetOriginPkgAddress + +```go +func TestSetOriginPkgAddress(addr Address) +``` +Sets the call entry realm address to **addr**. + +#### Usage +```go +std.TestSetOriginPkgAddress(std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec")) +``` + +--- + +### TestSetOriginSend + +```go +func TestSetOriginSend(sent, spent Coins) +``` +Sets the sent & spent coins for the current context. + +#### Usage +```go +std.TestSetOriginSend(sent, spent Coins) +``` +--- + +### TestIssueCoins + +```go +func TestIssueCoins(addr Address, coins Coins) +``` + +Issues testing context **coins** to **addr**. + +#### Usage + +```go +issue := std.Coins{{"coin1", 100}, {"coin2", 200}} +addr := std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") +std.TestIssueCoins(addr, issue) +``` + +--- + +### TestSetRealm + +```go +func TestSetRealm(rlm Realm) +``` + +Sets the realm for the current frame. After calling `TestSetRealm()`, calling +[`CurrentRealm()`](#currentrealm) in the same test function will yield the value of `rlm`, and +any `PreviousRealm()` called from a function used after TestSetRealm will yield `rlm`. + +Should be used in combination with [`NewUserRealm`](#newuserrealm) & +[`NewCodeRealm`](#newcoderealm). + +#### Usage +```go +addr := std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") +std.TestSetRealm(std.NewUserRealm("")) +// or +std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) +``` + +--- + +### NewUserRealm + +```go +func NewUserRealm(address Address) Realm +``` + +Creates a new user realm for testing purposes. + +#### Usage +```go +addr := std.Address("g1ecely4gjy0yl6s9kt409ll330q9hk2lj9ls3ec") +userRealm := std.NewUserRealm(addr) +``` + +--- + +### NewCodeRealm + +```go +func NewCodeRealm(pkgPath string) Realm +``` + +Creates a new code realm for testing purposes. + +#### Usage +```go +path := "gno.land/r/demo/boards" +codeRealm := std.NewCodeRealm(path) +``` diff --git a/docs/resources/gno-testing.md b/docs/resources/gno-testing.md new file mode 100644 index 00000000000..d0ddb446816 --- /dev/null +++ b/docs/resources/gno-testing.md @@ -0,0 +1,170 @@ +# Running & testing Gno code + +## Prerequisites + +- `gno` set up. See [Installation](../users/interact-with-gnokey.md). + +## Overview + +In this tutorial, you will learn how to run and test your Gno code locally, by +using the `gno` binary. For this example, we will use the `Counter` code from a +[previous tutorial](../builders/anatomy-of-a-gno-package.md). + +## Setup + +Start by creating a folder which will contain your Gno code: + +``` +mkdir counter +cd counter +``` + +First, we should initialize a `gno.mod` file. This file declares the package path +of your realm, and is used by Gno tools. We can do this by using the following command: + +``` +gno mod init gno.land/r//counter +``` + +You can enter your username under ``. In this case, let's use `example`. +This will create a file with the following content: + +``` +module gno.land/r/example/counter +``` + +Then, in the same folder, create two files- `counter.gno` & `counter_test.gno`: + +``` +touch counter.gno counter_test.gno +``` + +In these files, paste in the code from the previous tutorial. + +`counter.gno`: +```go +package counter + +import "strconv" + +var count int + +func Increment(change int) int { + count += change + return count +} + +func Render(_ string) string { + return "Current counter value: " + strconv.Itoa(count) +} +``` + +`counter_test.gno`: +```go +package counter + +import "testing" + +func TestIncrement(t *testing.T) { + // Check initial value + if count != 0 { + t.Fatalf("Expected 0, got %d", count) + } + + // Call Increment + value := Increment(42) + + // Check result + if value != 42 { + t.Fatalf("Expected 42, got %d", count) + } +} +``` + +## Testing + +To run package tests, we can simply use the `gno test` subcommand, passing it the +directory that contains the tests. From inside the `counter/` directory, we +can run the following: + +``` +$ gno test . +ok . 0.81s +``` + +To see a detailed list of tests that were executed, we can add the verbose flag: + +``` +$ gno test . -v +=== RUN TestIncrement +--- PASS: TestIncrement (0.00s) +ok . 0.81s +``` + +Apart from `-v`, other flags are also available, such as ones for setting the +test timeout, checking performance metrics, etc. + +:::info Mocked testing & running environment +The `gno` binary mocks a blockchain environment when running & testing code. +See [Final remarks](#final-remarks). +::: + +## Running Gno code + +The `gno` binary contains a `run` subcommand, allowing users to evaluate +specific expressions in their Gno code, without a full blockchain environment. +Under the hood, the Gno Virtual Machine evaluates the given expression and simply +returns the value, without any permanent changes to contract storage. + +This can be an easy way to quickly evaluate a function during development, without +having to spin up a full blockchain environment. + +The GnoVM won't automatically print out return values upon evaluating expressions, +which is why we need to include a `println()` somewhere- either in the function +body itself, or modify the expression itself: + +``` +gno run -expr "println(Increment(42))" +``` + +Try running this expression for yourself: + +```go gno run-expression=println(Increment(42)) +package counter + +import "strconv" + +var count int + +func Increment(change int) int { + count += change + return count +} + +func Render(_ string) string { + return "Current counter value: " + strconv.Itoa(count) +} +``` + +The `run` subcommand also supports a full GnoVM debugger, which can be started +with the `-debug` flag. Read more about it [here](https://gno.land/r/gnoland/blog:p/gno-debugger). + +## Final remarks + +Note that executing and testing code as shown in this tutorial utilizes a local, +mocked execution environment. During testing and expression evaluation, the GnoVM +is simply running as a language interpreter, with no connection to a real blockchain. + +No real on-chain transactions occur, and the state changes are purely in-memory +for testing and development purposes. You might notice this if you run the same +expression modifying a state variable twice, with the actual value staying unchanged. + +All possible imports in your code are resolved from the GnoVM's installation folder. + +## Conclusion + +That's it 🎉 + +You've successfully run local tests and expressions using the `gno` binary. +Next, let's jump into how to create a Gno key pair, which is crucial to deploying +your code and interacting with the gno.land blockchain. diff --git a/docs/resources/gnoland-networks.md b/docs/resources/gnoland-networks.md new file mode 100644 index 00000000000..5eafea5bb77 --- /dev/null +++ b/docs/resources/gnoland-networks.md @@ -0,0 +1,187 @@ +# Gno networks + +## Network configurations + +| Network | RPC Endpoint | Chain ID | +|-------------|----------------------------------|---------------| +| Portal Loop | https://rpc.gno.land:443 | `portal-loop` | +| Test5 | https://rpc.test5.gno.land:443 | `test5` | + +### WebSocket endpoints +All networks follow the same pattern for websocket connections: + +```shell +wss:///websocket +``` + +## Staging Environments (Portal Loops) + +XXX: tell that portal loop is currently using a custom code but will switch to a gnodev powered alternative, usable by anyone to run a staging + +Portal Loop is an always-up-to-date staging testnet that allows for using +the latest version of Gno, gno.land, and TM2. By utilizing the power of Docker +& the [tx-archive](https://github.com/gnolang/tx-archive) tool, the Portal Loop +can run the latest code from the master branch on the [Gno monorepo](https://github.com/gnolang/gno), +while preserving most/all the previous transaction data. + +The Portal Loop allows for quick iteration on the latest version of Gno - without +having to make a hard/soft fork. + +Below is a diagram demonstrating how the Portal Loop works: +``` + +----------------------------------+ + | Portal Loop running | < ----+ + +----------------------------------+ | + | | + | | + v | + +----------------------------------+ | + | Detect changes in 'master' | | + +----------------------------------+ | + | | + | | + v | + +----------------------------------+ | + | Archive transaction data & state | | + +----------------------------------+ | + | | + | | + v | + +----------------------------------+ | + | Load changes from 'master' | | + +----------------------------------+ | + | | + | | + v | + +----------------------------------+ | + | Replay transaction data | ------+ + +----------------------------------+ +``` + +Specifically, Portal Loop behaves like a normal network until a change is detected +in the `master` branch in the Gno monorepo. At this point, Portal Loop archives +on-chain data using the [tx-archive](https://github.com/gnolang/tx-archive) +tool, saving all transactions that happened on it thus far. + +It then pulls the latest changes from the `master` branch, and inserts all +previously archived transactions into the genesis of the newly deployed chain. +After genesis has been replayed, the chain continues working as normal. + +### Using the Portal Loop + +The Portal Loop deployment can be found at [gno.land](https://gno.land), while +the exposed RPC endpoints can be found on `https://rpc.gno.land:443`. + +XXX: list or link to the list of available RPC endpoints. + +#### A warning note + +While allowing for quick iteration on the most up-to-date software, the Portal Loop +has some drawbacks: +- If a breaking change happens on `master`, transactions that used the previous version of +Gno will fail to be replayed, meaning **data will be lost**. +- Since transactions are archived and replayed during genesis, +block height & timestamp cannot be relied upon. + +#### Deploying to the Portal Loop + +There are two ways to deploy code to the Portal Loop: + +1. *automatic* - all packages in found in the `examples/gno.land/{p,r}/` directory in the [Gno monorepo](https://github.com/gnolang/gno) get added to the + new genesis each cycle, +2. *permissionless* - this includes replayed transactions with `addpkg`, and + new transactions you can issue with `gnokey maketx addpkg`. + +Since the packages in `examples/gno.land/{p,r}` are deployed first, +permissionless deployments get superseded when packages with identical `pkgpath` +get merged into `examples/`. + +The above mechanism is also how the `examples/` on the Portal Loop +get collaboratively iterated upon, which is its main mission. + +## Gno Testnets + +gno.land testnets are categorized by 4 main points: +- **Persistence of state** + - Is the state and transaction history persisted? +- **Timeliness of code** + - How up-to-date are Gno language features and demo packages & realms? +- **Intended purpose** + - When should this testnet be used? +- **Versioning strategy** + - How is this testnet versioned? + +Below you can find a breakdown of each existing testnet by these categories. + +### Portal Loop + +Portal Loop is an always up-to-date rolling testnet. It is meant to be used as +a nightly build of the Gno tech stack. The home page of [gno.land](https://gno.land) +is the `gnoweb` render of the Portal Loop testnet. + +- **Persistence of state:** + - State is kept on a best-effort basis + - Transactions that are affected by breaking changes will be discarded +- **Timeliness of code:** + - Packages & realms which are available in the `examples/` folder on the + [Gno monorepo](https://github.com/gnolang/gno) exist on the Portal Loop in + matching state - they are refreshed with every new commit to the `master` + branch. +- **Intended purpose** + - Providing access the latest version of Gno for fast development & demoing +- **Versioning strategy**: + - Portal Loop infrastructure is managed within the + [`misc/loop`](https://github.com/gnolang/gno/tree/master/misc/loop) folder in the + monorepo + +### Test5 + +Test5 a permanent multi-node testnet. It bumped the validator set from 7 to 17 +nodes, introduced GovDAO V2, and added lots of bug fixes and quality of life +improvements. + +Test5 was launched in November 2024. + +- **Persistence of state:** + - State is fully persisted unless there are breaking changes in a new release, + where persistence partly depends on implementing a migration strategy +- **Timeliness of code:** + - Pre-deployed packages and realms are at monorepo commit [2e9f5ce](https://github.com/gnolang/gno/tree/2e9f5ce8ecc90ee81eb3ae41c06bab30ab926150) +- **Intended purpose** + - Running a full node, testing validator coordination, deploying stable Gno + dApps, creating tools that require persisted state & transaction history +- **Versioning strategy**: + - Test5 is to be release-based, following releases of the Gno tech stack. + +### TestX + +These testnets are deprecated and currently serve as archives of previous progress. + +## Test4 (archive) + +Test4 is the first permanent multi-node testnet. Archived data for test4 can be +found [here](https://github.com/gnolang/tx-exports/tree/main/test4.gno.land). + +Launch date: July 10th 2024 +Release commit: [194903d](https://github.com/gnolang/gno/commit/194903db0350ace7d57910e6c34125d3aa9817da) + +### Test3 (archive) + +The third Gno testnet. Archived data for test3 can be found [here](https://github.com/gnolang/tx-exports/tree/main/test3.gno.land). + +Launch date: November 4th 2022 +Release commit: [1ca2d97](https://github.com/gnolang/gno/commit/1ca2d973817b174b5b06eb9da011e1fcd2cca575) + +### Test2 (archive) + +The second Gno testnet. Find archive data [here](https://github.com/gnolang/tx-exports/tree/main/test2.gno.land). + +Launch date: July 10th 2022 +Release commit: [652dc7a](https://github.com/gnolang/gno/commit/652dc7a3a62ee0438093d598d123a8c357bf2499) + +### Test1 (archive) + +The first Gno testnet. Find archive data [here](https://github.com/gnolang/tx-exports/tree/main/test1.gno.land). + +Launch date: May 6th 2022 +Release commit: [797c7a1](https://github.com/gnolang/gno/commit/797c7a132d65534df373c63b837cf94b7831ac6e) diff --git a/docs/reference/go-gno-compatibility.md b/docs/resources/go-gno-compatibility.md similarity index 82% rename from docs/reference/go-gno-compatibility.md rename to docs/resources/go-gno-compatibility.md index 9f9d611e4fd..eb590297d16 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/resources/go-gno-compatibility.md @@ -1,9 +1,7 @@ ---- -id: go-gno-compatibility ---- - # Go - Gno compatibility +Gno is modeled after Go 1.17. + ## Reserved keywords | keyword | support | @@ -54,7 +52,7 @@ rune := rune('a') | `uintptr`, `unsafe.Pointer` | missing | missing | | `string` | full | full | | `rune` | full | full | -| `interface{}` | full | full | +| `interface{}` / `any` | full | full | | `[]T` (slices) | full | full\* | | `[N]T` (arrays) | full | full\* | | `map[T1]T2` | full | full\* | @@ -272,7 +270,7 @@ Legend: [^1]: `builtin` is a "fake" package that exists to document the behaviour of some builtin functions. The "fake" package does not currently exist in Gno, but [all functions up to Go 1.17 exist](https://pkg.go.dev/builtin@go1.17), - except for those relating to complex or channel types. + except for those relating to complex (real or imag) or channel types. [^2]: `crypto/sha1` and `crypto/md5` implement "deprecated" hashing algorithms, widely considered unsafe for cryptographic hashing. Decision on whether to include these as part of the official standard libraries is still @@ -297,31 +295,31 @@ Legend: ## Tooling (`gno` binary) -| go command | gno command | comment | -|-------------------|---------------------------|-----------------------------------------------------------------------| -| go bug | gno bug | same behavior | -| go build | gno transpile -gobuild | same intention, limited compatibility | -| go clean | gno clean | same intention, limited compatibility | -| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 | -| go env | gno env | | -| go fix | | | -| go fmt | gno fmt | gofmt (& similar tools, like gofumpt) works on gno code. | -| go generate | | | -| go get | | see `gno mod download`. | -| go help | gno $cmd --help | ie. `gno doc --help` | -| go install | | | -| go list | | | -| go mod | gno mod | | -| + go mod init | gno mod init | same behavior | -| + go mod download | gno mod download | same behavior | -| + go mod tidy | gno mod tidy | same behavior | -| + go mod why | gno mod why | same intention | -| | gno transpile | | -| go work | | | -| | gno repl | | -| go run | gno run | | -| go test | gno test | limited compatibility | -| go tool | | | -| go version | | | -| go vet | | | -| golint | gno lint | same intention | +| go command | gno command | comment | +|-------------------|------------------------------|-----------------------------------------------------------------------| +| go bug | gno bug | same behavior | +| go build | gno tool transpile -gobuild | same intention, limited compatibility | +| go clean | gno clean | same intention, limited compatibility | +| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 | +| go env | gno env | | +| go fix | | | +| go fmt | gno fmt | gofmt (& similar tools, like gofumpt) works on gno code. | +| go generate | | | +| go get | | see `gno mod download`. | +| go help | gno $cmd --help | ie. `gno doc --help` | +| go install | | | +| go list | | | +| go mod | gno mod | | +| + go mod init | gno mod init | same behavior | +| + go mod download | gno mod download | same behavior | +| + go mod tidy | gno mod tidy | same behavior | +| + go mod why | gno mod why | same intention | +| | gno tool transpile | | +| go work | | | +| | gno tool repl | | +| go run | gno run | | +| go test | gno test | limited compatibility | +| go tool | | | +| go version | | | +| go vet | | | +| golint | gno tool lint | same intention | diff --git a/docs/resources/realms.md b/docs/resources/realms.md new file mode 100644 index 00000000000..ab551b9935f --- /dev/null +++ b/docs/resources/realms.md @@ -0,0 +1,157 @@ +# Realms + +In gno.land, realms are entities that are addressable and identifiable by a +[Gno address](./gno-stdlibs.md#address). These can be user +realms (EOAs), as well as smart contract realms. Realms have several +properties: +- They can own, receive & send [Coins](./gno-stdlibs.md#coin) through the + [Banker](./gno-stdlibs.md#banker) module +- They can be part of a transaction call stack, as a caller or a callee +- They can be with or without code - smart contracts, or EOAs + +Realms are represented by a `Realm` type in Gno: +```go +type Realm struct { + addr Address // Gno address in the bech32 format + pkgPath string // realm's path on-chain +} +``` + +The full Realm API can be found under the +[reference section](./gno-stdlibs.md). + +### Smart Contract Realms + +Often simply called `realms`, Gno smart contracts contain Gno code and exist +on-chain at a specific [package path](gno-packages.md). A package path is the +defining identifier of a realm, while its address is derived from it. + +As opposed to [pure packages](./gno-packages.md#pure-packages-p), realms are +stateful, meaning they keep their state between transaction calls. In practice, +global variables used in realms are automatically persisted after a transaction +has been executed. Thanks to this, Gno developers do not need to bother with the +intricacies of state management and persistence, like they do with other +languages. + +### Externally Owned Accounts (EOAs) + +EOAs, or simply `user realms`, are Gno addresses generated from a BIP39 mnemonic +phrase in a key management application, such as +[gnokey](../users/interact-with-gnokey.md), and web wallets, such as +[Adena](../users/third-party-wallets.md). + +Currently, EOAs are the only realms that can initiate a transaction. They can do +this by calling any of the possible messages in gno.land, which can be +found [here](../users/interact-with-gnokey.md#making-transactions). + +### Working with realms + +In Gno, each transaction contains a realm call stack. Every item in the stack and +its properties can be accessed via different functions defined in the `std` +package in Gno: +- `std.GetOrigCaller()` - returns the address of the original signer of the + transaction +- `std.PrevRealm()` - returns the previous realm instance, which can be a user realm + or a smart contract realm +- `std.CurrentRealm()` - returns the instance of the realm that has called it + +Let's look at the return values of these functions in two distinct situations: +1. EOA calling a realm +2. EOA calling a sequence of realms + +#### 1. EOA calling a realm + +Take these two actors in the call stack: +``` +EOA: + addr: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 + pkgPath: "" // empty as this is a user realm + +Realm A: + addr: g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s + pkgPath: gno.land/r/demo/users + + ┌─────────────────────┐ ┌─────────────────────────┐ + │ EOA │ │ Realm A │ + │ │ │ │ + │ addr: │ │ addr: │ + │ g1jg...sqf5 ├──────► g17m...8v2s │ + │ │ │ │ + │ pkgPath: │ │ pkgPath: │ + │ "" │ │ gno.land/r/demo/users │ + └─────────────────────┘ └─────────────────────────┘ +``` + +Let's look at return values for each of the methods, called from within `Realm A`: +``` +std.GetOrigCaller() => `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` +std.PrevRealm() => Realm { + addr: `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` + pkgPath: `` +} +std.CurrentRealm() => Realm { + addr: `g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s` + pkgPath: `gno.land/r/demo/users`} +``` + +#### 2. EOA calling a sequence of realms + +Take these three actors in the call stack: +``` +EOA: + addr: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 + pkgPath: "" // empty as this is a user realm + +Realm A: + addr: g1dvqd8qgvavqayxklzfdmccd2eps263p43pu2c6 + pkgPath: gno.land/r/demo/a + +Realm B: + addr: g1rsk9cwv034cw3s6csjeun2jqypj0ztpecqcm3v + pkgPath: gno.land/r/demo/b + +┌─────────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐ +│ EOA │ │ Realm A │ │ Realm B │ +│ │ │ │ │ │ +│ addr: │ │ addr: │ │ addr: │ +│ g1jg...sqf5 ├───► g17m...8v2s ├───► g1rs...cm3v │ +│ │ │ │ │ │ +│ pkgPath: │ │ pkgPath: │ │ pkgPath: │ +│ "" │ │ gno.land/r/demo/a │ │ gno.land/r/demo/b │ +└─────────────────────┘ └──────────────────────┘ └─────────────────────┘ +``` + +Depending on which realm the methods are called in, the values will change. For +`Realm A`: +``` +std.GetOrigCaller() => `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` +std.PrevRealm() => Realm { + addr: `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` + pkgPath: `` +} +std.CurrentRealm() => Realm { + addr: `g1dvqd8qgvavqayxklzfdmccd2eps263p43pu2c6` + pkgPath: `gno.land/r/demo/a` +} +``` + +For `Realm B`: +``` +std.GetOrigCaller() => `g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5` +std.PrevRealm() => Realm { + addr: `g1dvqd8qgvavqayxklzfdmccd2eps263p43pu2c6` + pkgPath: `gno.land/r/demo/a` +} +std.CurrentRealm() => Realm { + addr: `g1rsk9cwv034cw3s6csjeun2jqypj0ztpecqcm3v` + pkgPath: `gno.land/r/demo/b` +} +``` + +For more information about realms and how they fit into the gno.land ecosystem, +see the [Package Path Structure](./gno-packages.md#package-path-structure) +documentation. + +To learn how to develop your own realms, check out the +[Anatomy of a Gno Package](../builders/anatomy-of-a-gno-package.md) and +[Example Minisocial dApp](../builders/example-minisocial-dapp.md) guides. diff --git a/docs/resources/users-and-teams.md b/docs/resources/users-and-teams.md new file mode 100644 index 00000000000..c44bd1ed899 --- /dev/null +++ b/docs/resources/users-and-teams.md @@ -0,0 +1,89 @@ +# Users and Teams in gno.land + +## User Registration + +In gno.land, users can register a unique username that: +- Provides a more readable identity than a blockchain address +- Grants the exclusive right to deploy code under that namespace +- Can be used in social contexts across the ecosystem + +### Registration Process + +To register a username: + +1. Visit the user registry realm at [`gno.land/r/gnoland/users`](https://gno.land/r/gnoland/users) +2. Check if your desired username is available by +3. Register using the following command: + +```bash +gnokey maketx call \ + --pkgpath "gno.land/r/gnoland/users/v1" \ + --func "Register" \ + --args "YOUR_USERNAME" \ + --gas-fee 1000000ugnot \ + --gas-wanted 2000000 \ + --send "1000000ugnot" \ + --remote https://rpc.gno.land:443 \ + --chainid portal-loop \ + YOUR_KEY_NAME +``` + +The registration costs 1 GNOT, which serves as an anti-spam measure and ensures users value their identities. + +## Username Ownership + +Once registered, a username is permanently linked to the registering address. This address: + +- Has exclusive rights to deploy packages under that namespace +- Can manage that username's profile and + + +### Namespace Access + +Your registered username becomes an exclusive namespace where you can deploy both realms and pure packages: + +``` +gno.land/p/YOUR_USERNAME/... # Pure packages +gno.land/r/YOUR_USERNAME/... # Realms +``` + +For example, a user with the username "alice" could deploy: +- `gno.land/r/alice/blog` - A personal blog realm +- `gno.land/p/alice/utils` - A utility package + +Only the address that registered the username can deploy to these paths, ensuring security and preventing impersonation. + +## Teams and Collaborative Development + +There is an ongoing effort on team-based development through shared namespaces. This feature will enable: + +1. Multiple addresses with permission to deploy under a team namespace +2. Role-based access control for team members +3. Collaborative development of larger projects + +Until full team support is available, collaborative development can be achieved through: + +1. **Account sharing** - Multiple developers using the same key (not recommended for security reasons) +2. **Multi-signature wallets** - Using multi-sig wallets to control deployment to a shared namespace +3. **Development on branches** - Developing under individual namespaces and then migrating to a main namespace + +## Public Anonymous Namespaces + +In addition to registered usernames, all addresses can deploy under their own address-based namespace: + +``` +gno.land/p/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5/... +gno.land/r/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5/... +``` + +This allows for permissionless deployment even without a registered username, though these long namespaces are less user-friendly. + +## Related Resources + +For more information on users and namespaces, refer to: + +- [Gno Packages](./gno-packages.md) - Understand how namespaces work within the package system +- [Realms](./realms.md) - Learn about stateful applications that can be deployed under your namespace +- [Deploying Packages](../builders/deploy-packages.md) - Instructions for deploying code under your namespace + +To explore registered users, visit the [User Registry](https://gno.land/r/gnoland/users) on the Portal Loop network. diff --git a/docs/testing-guide.md b/docs/testing-guide.md deleted file mode 100644 index 6019f238f01..00000000000 --- a/docs/testing-guide.md +++ /dev/null @@ -1,86 +0,0 @@ -# Gnoland Testing Guide - -This guide provides an overview of our testing practices and conventions. While most of our testing aligns with typical Go practices, there are exceptions and specifics you should be aware of. - -## Standard Package Testing - -For most packages, tests are written and executed in the standard Go manner: - -- Tests are located alongside the code they test. -- The `go test` command can be used to execute tests. - -However, as mentioned earlier, there are some exceptions. In the following sections, we will explore our specialized tests and how to work with them. - -## Gno Filetests - -**Location:** `gnovm/test/files` - -These are our custom file-based tests tailored specifically for this project. - -**Execution:** - -From the gnovm directory, There are two main commands to run Gno filetests: - -1. To test native files, use: -``` -make _test.gnolang.native -``` - -2. To test standard libraries, use: -``` -make _test.gnolang.stdlibs -``` - -**Golden Files Update:** - -Golden files are references for expected outputs. Sometimes, after certain updates, these need to be synchronized. To do so: - -1. For native tests: -``` -make _test.gnolang.native.sync -``` - -2. For standard library tests: -``` -make _test.gnolang.stdlibs.sync -``` - -## Integration Tests - -**Location:** `gno.land/**/testdata` - -From the gno.land directory, Integration tests are designed to ensure different parts of the project work cohesively. Specifically: - -1. **InMemory Node Integration Testing:** - Found in `gno.land/cmd/gnoland/testdata`, these are dedicated to running integration tests against a genuine `gnoland` node. - -2. **Integration Features Testing:** - Located in `gno.land/pkg/integration/testdata`, these tests target integrations specific commands. - -These integration tests utilize the `testscript` package and follow the `txtar` file specifications. - -**Documentation:** - -- For general `testscript` package documentation, refer to: [testscript documentation](https://github.com/rogpeppe/go-internal/blob/v1.11.0/testscript/doc.go) - -- For more specific details about our integration tests, consult our extended documentation: [gnoland integration documentation](https://github.com/gnolang/gno/blob/master/gno.land/pkg/integration/doc.go) - -**Execution:** - -To run the integration tests (alongside other packages): - -``` -make _test.pkgs -``` - -**Golden Files Update within txtar:** - -For tests utilizing the `cmp` command inside `txtar` files, golden files can be synchronized using: - -``` -make _test.pkgs.sync -``` - ---- - -As the project evolves, this guide might be updated. diff --git a/docs/users/discover-gnoland.md b/docs/users/discover-gnoland.md new file mode 100644 index 00000000000..c09022895a2 --- /dev/null +++ b/docs/users/discover-gnoland.md @@ -0,0 +1,37 @@ +# Discover gno.land + +gno.land provides various ways to interact with the blockchain, from browsing +the deployed smart contracts (called realms) to interacting with them through +transactions. Here's how you can get started: + +### Exploring with gnoweb + +[gnoweb](./explore-with-gnoweb.md) is the built-in web interface for exploring +the gno.land blockchain. It allows you to: +- Browse deployed realms and packages +- View source code of any contract +- See rendered content from realms + +gnoweb is your window into the on-chain activity and provides a friendly way to +discover what's available on gno.land. + +### Using a Wallet + +To interact with gno.land (sending transactions, deploying contracts, etc.), +you'll need a wallet: + +- [gnokey](./interact-with-gnokey.md) - The official command-line wallet for gno.land +- [Third-party wallets](./third-party-wallets.md) - Browser extensions and other wallet options + +### Useful Resources + +- [Portal Loop](https://gno.land) - The main testnet for exploring gno.land +- [Faucet Hub](https://faucet.gno.land) - Get testnet tokens for experimenting +- [Awesome Gno](https://github.com/gnolang/awesome-gno) - A curated list of tools, tutorials, and projects + +## The gno.land Ecosystem + +gno.land is not limited to a single blockchain but is designed to become an +ecosystem of chains powered by the Gno language. The current networks focus on +testing and development, but the vision extends to a vibrant ecosystem of +interconnected chains and applications. diff --git a/docs/users/example-boards.md b/docs/users/example-boards.md new file mode 100644 index 00000000000..85447dcfc86 --- /dev/null +++ b/docs/users/example-boards.md @@ -0,0 +1,57 @@ +# Using the Boards Application on gno.land + +The Boards realm is one of the first applications on gno.land, offering a +decentralized discussion forum where anyone can create and participate in +conversations. This guide will walk you through discovering, exploring, and +interacting with the Boards realm. + +## Finding Boards + +The main Boards application can be found at +[gno.land/r/demo/boards](https://gno.land/r/demo/boards). When you visit this +URL, you'll see the rendered output of the Boards realm's `Render()` function, +which displays the current state of the forum. + +## Exploring Boards + +The Boards realm organizes content into: + +1. **Boards** - General topic categories (e.g., "Gno", "Random", "Meta") +2. **Threads** - Individual discussion topics within boards +3. **Posts** - Individual messages within threads + +You can navigate between these levels by clicking on the links in the rendered +output. Each level presents different information and options for interaction. + +For more details on browsing through realms and their content, see +[Exploring with gnoweb](./explore-with-gnoweb.md). + +## Interacting with Boards + +To interact with the Boards application (creating boards, threads, or posts), +you'll need: + +1. A gno.land account with some GNOT tokens +2. A way to sign and send transactions to the gno.land network + +You can interact with Boards through the command line using `gnokey`. For +detailed instructions on sending transactions to realms, see +[Interacting with gnokey](./interact-with-gnokey.md). + +## Viewing Your Contributions + +After interacting with the Boards realm, you can view your contributions by +navigating to the board where you posted and finding your thread or post in the +list. + +## Building Your Own Board + +Inspired by the Boards realm? You can create your own version by: + +1. Studying the [source code](https://gno.land/r/demo/boards:files/boards.gno) +2. Deploying a modified version to your own namespace +3. Adding your own features and improvements + +The Boards application showcases many of Gno's powerful features including state +persistence, rendered UI, and interactive functionality - making it an excellent +example to learn from. diff --git a/docs/users/explore-with-gnoweb.md b/docs/users/explore-with-gnoweb.md new file mode 100644 index 00000000000..5b4948d3ef1 --- /dev/null +++ b/docs/users/explore-with-gnoweb.md @@ -0,0 +1,95 @@ +# Exploring gno.land with gnoweb + +`gnoweb` is gno.land's universal web interface that lets you browse applications +and source code on any gno.land network. This guide explains how to use gnoweb +to explore the blockchain ecosystem. + +## Networks + +The main gnoweb instance is available at [gno.land](https://gno.land), which serves the Portal Loop network. + +For a complete list of all available networks (testnets and more), see [Networks](../resources/gnoland-networks.md). + +## Understanding Code Organization + +Before diving into `gnoweb`, we need to cover a fundamental concept in gno.land: +code organization. + +gno.land can host two types of code: [realms](../resources/realms.md) (smart contracts), +and [pure packages](../resources/gno-packages.md) (libraries). Realms can +contain and manage state, while pure packages are used for creating reusable +functionality, hence _pure_. + +gno.land employs a storage system which is similar to a classic file system - each +package lives on a specific package path. A typical gno.land package path, such +as `gno.land/r/gnoland/home`, contains the following components: + +``` + gno.land / r / gnoland / home +chain domain type namespace package name +``` + +Let's break it down: +- `chain domain` represents the domain of the chain. In this case, the domain is + simply `gno.land`. In the future, the ecosystem may expand to multiple chains + which could have different chain domains. +- `type` represents the type of package found on this path. There are two available + options - `p` & `r` - pure packages and realms, respectively. +- `namespace` is the namespace of the package. Namespaces can be registered using + the `gno.land/r/gnoland/users` realms, granting a user permission to deploy under + that specific namespace. +- `package name` represents the name of the package found on the path. This part has + to match the top-level package declaration in Gno files. + +## Viewing Rendered Content + +Realms can implement a special `Render()` function that returns HTML-like content: + +`gnoweb` is a minimalistic web server that serves as a unified frontend for all +realms in gno.land. It uses ABCI queries to get the latest state of a specific +realm from the gno.land network. + +Let's dive into how this works. + +### Realm state rendering + +In line with minimalistic principles, gno.land encourages developers to implement +a `Render()` function within their realms, allowing them to create a Markdown view +for how their realms will be rendered. `gnoweb` utilizes a built-in Markdown renderer +that uses the output of the `Render()` function as its content source. + +A simple example of a realm utilizing the Render function can be found below: + +```go +package hello + +func Render(path string) string { + if path == "" { + return "# Hello, 世界!" + } + + return "# Hello, " + path +} +``` + +Based on the provided path, `gnoweb` queries the gno.land network using the +`qrender` ABCI query. It then renders the response data as Markdown. + +The realm above can be found on the Portal Loop at [`gno.land/r/docs/hello`](https://gno.land/r/docs/hello). + +While JS/TS clients for Gno exist and developers can create custom websites for their +gno.land applications as they see fit, the approach `gnoweb` takes with `Render()` +is a surefire way for simplicity and ease of development. + +:::info `Render()` is optional +Developers can but do not have to provide a `Render()` function in their realms. +Custom getter methods tailored to the specifics of the realm can be built instead. +::: + +### Viewing source code + +All code uploaded to gno.land is open-source and available for everyone to see, +by design. + +Visit the [`gno.land/r/docs/source`](https://gno.land/r/docs/source) realm to learn +how you can do this. diff --git a/docs/users/interact-with-gnokey.md b/docs/users/interact-with-gnokey.md new file mode 100644 index 00000000000..ddcf40179ee --- /dev/null +++ b/docs/users/interact-with-gnokey.md @@ -0,0 +1,963 @@ +# Interacting with gno.land using gnokey + +`gnokey` is the official command-line wallet and utility for interacting with +gno.land networks. It allows you to manage keys, query the blockchain, send +transactions, and deploy smart contracts. This guide will help you get started +with the essential operations. + +## Installing gnokey + +You can install `gnokey` through various methods: + +### Option 1: Install from source + +To build and install from source, you'll need: +- Git +- Go 1.22+ +- Make + +```bash +# Clone the repository +git clone https://github.com/gnolang/gno.git +cd gno + +# Install gnokey +make install +``` + +### Option 2: Download prebuilt binaries + +Coming soon. + +## Managing key pairs + +In this tutorial, you will learn how to create your Gno key pair using +[gnokey](./interact-with-gnokey.md). A key pair is required to send +transactions to the blockchain, including deploying code, interacting with +existing applications, and more. + +## A word about key pairs + +Key pairs are the foundation of how users interact with blockchains; and Gno is +no exception. By using a 12-word or 24-word [mnemonic phrase](https://www.zimperium.com/glossary/mnemonic-seed/) +as a source of randomness, users can derive a private and a public key. +These two keys can then be used further; a public key derives an address which is +a unique identifier of a user on the blockchain, while a private key is used for +signing messages and transactions for the aforementioned address, proving a user +has ownership over it. + +Let's see how we can use `gnokey` to generate a Gno key pair locally. + +## Generating a key pair + +The `gnokey add` command allows you to generate a new key pair locally. Simply +run the command, while adding a name for your key pair: + +```bash +gnokey add MyKey +``` + +After running the command, `gnokey` will ask you to enter a password that will be +used to encrypt your key pair to the disk. Then, it will show you the following +information: +- Your public key, as well as the Gno address derived from it, starting with `g1`, +- Your randomly generated 12-word mnemonic phrase which was used to derive the key pair. + +:::warning Safeguard your mnemonic phrase! + +A **mnemonic phrase** is like your master password; you can use it over and over +to derive the same key pairs. This is why it is crucial to store it in a safe, +offline place - writing the phrase on a piece of paper and hiding it is highly +recommended. **If it gets lost, it is unrecoverable.** + +::: + +`gnokey` will generate a keybase in which it will store information about your +key pairs. The keybase directory path is stored under the `-home` flag in `gnokey`. + +### Gno addresses + +Your **Gno address** is like your unique identifier on the network; an address +is visible in the caller stack of an application, it is included in each +transaction you create with your key pair, and anyone who knows your address can +send you [coins](../resources/gno-stdlibs.md#coin), etc. + +## Making transactions + +In Gno, there are four types of messages that can change on-chain state: +- `AddPackage` - adds new code to the chain +- `Call` - calls a specific path and function on the chain +- `Send` - sends coins from one address to another +- `Run` - executes a Gno script against on-chain code + +A gno.land transaction contains two main things: +- A base configuration where variables such as `gas-fee`, `gas-wanted`, and others + are defined +- A list of messages to execute on the chain + +Currently, `gnokey` supports single-message transactions, while multiple-message +transactions can be created in Go programs, supported by the +[gnoclient](https://github.com/gnolang/gno/tree/master/gno.land/pkg/gnoclient) package. + +We will need some testnet coins (GNOTs) for each state-changing call. Visit the [Faucet +Hub](https://faucet.gno.land) to get GNOTs for the Gno testnets that are currently live. + +Let's delve deeper into each of these message types. + +## `AddPackage` + +In case you want to upload new code to the chain, you can use the `AddPackage` +message type. You can send an `AddPackage` transaction with `gnokey` using the +following command: + +```bash +gnokey maketx addpkg +``` + +To understand how to use this subcommand better, let's write a simple "Hello world" +[pure package](../resources/gno-packages.md). First, let's create a folder which will +store our example code. + +```bash +└── example/ +``` + +Then, let's create a `hello_world.gno` file under the `p/` folder: + +```bash +cd example +mkdir p/ && cd p +touch hello_world.gno +``` + +Now, we should have the following folder structure: + +```bash +└── example/ +│ └── p/ +│ └── hello_world.gno +``` + +In the `hello_world.gno` file, add the following code: + +```go +package hello_world + +func Hello() string { + return "Hello, world!" +} +``` + +We are now ready to upload this package to the chain. To do this, we must set the +correct flags for the `addpkg` subcommand. + +The `addpkg` subcommmand uses the following flags and arguments: +- `-pkgpath` - on-chain path where your code will be uploaded to +- `-pkgdir` - local path where your is located +- `-broadcast` - enables broadcasting the transaction to the chain +- `-deposit` - a deposit amount of GNOT to send along with the transaction +- `-gas-wanted` - the upper limit for units of gas for the execution of the + transaction +- `-gas-fee` - amount of GNOTs to pay per gas unit +- `-chain-id` - id of the chain that we are sending the transaction to +- `-remote` - specifies the remote node RPC listener address + +The `-pkgpath`, `-pkgdir`, and `-deposit` flags are unique to the `addpkg` +subcommand, while `-broadcast`, `-gas-wanted`, `-gas-fee`, `-chain-id`, and +`-remote` are used for setting the base transaction configuration. These flags +will be repeated throughout the tutorial. + +Next, let's configure the `addpkg` subcommand to publish this package to the +[Portal Loop](../resources/gnoland-networks.md) testnet. Assuming we are in +the `example/p/` folder, the command will look like this: + +```bash +gnokey maketx addpkg \ +-pkgpath "gno.land/p//hello_world" \ +-pkgdir "." \ +-deposit "" \ +-gas-fee 10000000ugnot \ +-gas-wanted 8000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" +``` + +Once we have added a desired [namespace](../resources/users-and-teams.md) to upload the package to, we can specify a key pair name to use to execute the +transaction: + +```bash +gnokey maketx addpkg \ +-pkgpath "gno.land/p/examplenamespace/hello_world" \ +-pkgdir "." \ +-send "" \ +-gas-fee 10000000ugnot \ +-gas-wanted 200000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" +mykey +``` + +If the transaction was successful, you will get an output from `gnokey` that is +similar to the following: + +```console +OK! +GAS WANTED: 200000 +GAS USED: 117564 +HEIGHT: 3990 +EVENTS: [] +TX HASH: Ni8Oq5dP0leoT/IRkKUKT18iTv8KLL3bH8OFZiV79kM= +``` + +Let's analyze the output, which is standard for any `gnokey` transaction: +- `GAS WANTED: 200000` - the original amount of gas specified for the transaction +- `GAS USED: 117564` - the gas used to execute the transaction +- `HEIGHT: 3990` - the block number at which the transaction was executed at +- `EVENTS: []` - [Gno events](../resources/gno-stdlibs.md#events) emitted by the transaction, in this case, none +- `TX HASH: Ni8Oq5dP0leoT/IRkKUKT18iTv8KLL3bH8OFZiV79kM=` - the hash of the transaction + +Congratulations! You have just uploaded a pure package to the Portal Loop network. +If you wish to deploy to a different network, find the list of all network +configurations in the [Network Configuration](../resources/gnoland-networks.md) section. + +## `Call` + +The `Call` message type is used to call any exported realm function. +You can send a `Call` transaction with `gnokey` using the following command: + +```bash +gnokey maketx call +``` + +:::info `Call` uses gas + +Using `Call` to call an exported function will use up gas, even if the function +does not modify on-chain state. If you are calling such a function, you can use +the `query` functionality for a read-only call which +does not use gas. + +::: + +For this example, we will call the `wugnot` realm, which wraps GNOTs to a +GRC20-compatible token called `wugnot`. We can find this realm deployed on the +[Portal Loop](../resources/gnoland-networks.md) testnet, under the `gno.land/r/demo/wugnot` path. + +We will wrap `1000ugnot` into the equivalent in `wugnot`. To do this, we can call +the `Deposit()` function found in the `wugnot` realm. As previously, we will +configure the `maketx call` subcommand: + +```bash +gnokey maketx call \ +-pkgpath "gno.land/r/demo/wugnot" \ +-func "Deposit" \ +-send "1000ugnot" \ +-gas-fee 10000000ugnot \ +-gas-wanted 2000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" \ +mykey +``` + +In this command, we have specified three main things: +- The path where the realm lives on-chain with the `-pkgpath` flag +- The function that we want to call on the realm with the `-func` flag +- The amount of `ugnot` we want to send to be wrapped, using the `-send` flag + +Apart from this, we have also specified the Portal Loop chain ID, `portal-loop`, +as well as the Portal Loop remote address, `https://rpc.gno.land:443`. + +After running the command, we can expect an output similar to the following: +```bash +OK! +GAS WANTED: 2000000 +GAS USED: 489528 +HEIGHT: 24142 +EVENTS: [{"type":"Transfer","attrs":[{"key":"from","value":""},{"key":"to","value":"g125em6arxsnj49vx35f0n0z34putv5ty3376fg5"},{"key":"value","value":"1000"}],"pkg_path":"gno.land/r/demo/wugnot","func":"Mint"}] +TX HASH: Ni8Oq5dP0leoT/IRkKUKT18iTv8KLL3bH8OFZiV79kM= +``` + +In this case, we can see that the `Deposit()` function emitted an +[event](../resources/gno-stdlibs.md#events) that tells us more about what +happened during the transaction. + +After broadcasting the transaction, we can verify that we have the amount of `wugnot` we expect. We +can call the `BalanceOf()` function in the same realm: + +```bash +gnokey maketx call \ +-pkgpath "gno.land/r/demo/wugnot" \ +-func "BalanceOf" \ +-args "" \ +-gas-fee 10000000ugnot \ +-gas-wanted 2000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" \ +mykey +``` + +If everything was successful, we should get something similar to the following +output: + +``` +(1000 uint64) + +OK! +GAS WANTED: 2000000 +GAS USED: 396457 +HEIGHT: 64839 +EVENTS: [] +TX HASH: gQP9fJYrZMTK3GgRiio3/V35smzg/jJ62q7t4TLpdV4= +``` + +At the top, you will see the output of the transaction, specifying the value and +type of the return argument. + +In this case, we used `maketx call` to call a read-only function, which simply +checks the `wugnot` balance of a specific address. This is discouraged, as +`maketx call` actually uses gas. To call a read-only function without spending gas, +check out the `vm/qeval` query section. + +## `Send` + +We can use the `Send` message type to access the TM2 [Banker](../resources/gno-stdlibs.md#banker) +directly and transfer coins from one Gno address to another. + +Coins, such as GNOTs, are always formatted in the following way: + +``` + +100ugnot +``` + +For this example, let's transfer some GNOTs. Just like before, we can configure +our `maketx send` subcommand: +```bash +gnokey maketx send \ +-to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 \ +-send 100ugnot \ +-gas-fee 10000000ugnot \ +-gas-wanted 2000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" \ +mykey +``` + +Here, we have set the `-to` & `-send` flags to match the recipient, in this case +the publicly-known `test1` address, and `100ugnot` for the coins we want to send, +respectively. + +To check the balance of a specific address, check out the `bank/balances` query +in the [Querying a network](querying-a-network.md#bankbalances) section. + +## `Run` + +With the `Run` message, you can write a snippet of Gno code and run it against +code on the chain. For this example, we will use the [Userbook realm](https://gno.land/r/demo/userbook), +which simply allows you to register the fact that you have interacted with it. +It contains a simple `SignUp()` function, which we will call with `Run`. + +To understand how to use the `Run` message better, let's write a simple `script.gno` +file. First, create a folder which will store our script. + +```bash +└── example/ +``` + +Then, let's create a `script.gno` file: + +```bash +cd example +touch script.gno +``` + +Now, we should have the following folder structure: + +```bash +└── example/ +│ └── script.gno +``` + +In the `script.gno` file, first define the package to be `main`. Then we can import +the Userbook realm and define a `main()` function with no return values that will +be automatically detected and run. In it, we can call the `SignUp()` function. + +```go +package main + +import "gno.land/r/demo/userbook" + +func main() { + println(userbook.SignUp()) +} +``` + +Now we will be able to provide this to the `maketx run` subcommand: +```bash +gnokey maketx run \ +-gas-fee 1000000ugnot \ +-gas-wanted 20000000 \ +-broadcast \ +-chainid portal-loop \ +-remote "https://rpc.gno.land:443" \ +mykey ./script.gno +``` + +After running this command, the chain will execute the script and apply any state +changes. Additionally, by using `println`, which is only available in the `Run` +& testing context, we will be able to see the return value of the function called. + +### The power of `Run` + +Specifically, the above example could have been replaced with a simple `maketx call` +call. The full potential of run comes out in three specific cases: +1. Calling realm functions multiple times in a loop +2. Calling functions with non-primitive input arguments +3. Calling methods on exported variables + +Let's look at each of these cases in detail. To demonstrate, we'll make a call +to the following example realm: + +```go +package foo + +import "gno.land/p/demo/ufmt" + +var ( + MainFoo *Foo + foos []*Foo +) + +type Foo struct { + bar string + baz int +} + +func init() { + MainFoo = &Foo{bar: "mainBar", baz: 0} +} + +func (f *Foo) String() string { + return ufmt.Sprintf("Foo - (bar: %s) - (baz: %d)\n\n", f.bar, f.baz) +} + +func NewFoo(bar string, baz int) *Foo { + return &Foo{bar: bar, baz: baz} +} + +func AddFoos(multipleFoos []*Foo) { + foos = append(foos, multipleFoos...) +} + +func Render(_ string) string { + var output string + + for _, f := range foos { + output += f.String() + } + + return output +} +``` + +This realm is deployed to [`gno.land/r/docs/examples/run/foo`](https://gno.land/r/docs/examples/run/foo/package.gno) +on the Portal Loop testnet. + +1. Calling realm functions multiple times in a loop: +```go +package main + +import ( + "gno.land/r/docs/examples/run/foo" +) + +func main() { + for i := 0; i < 5; i++ { + println(foo.Render("")) + } +} +``` + +2. Calling functions with non-primitive input arguments: + +Currently, `Call` only supports primitives for arguments. With `Run`, these +limitations are removed; we can execute a function that takes in a struct, array, +or even an array of structs. + +We are unable to call `AddFoos()` with the `Call` message type, while with `Run`, +we can: + +```go +package main + +import ( + "strconv" + + "gno.land/r/docs/examples/run/foo" +) + +func main() { + var multipleFoos []*foo.Foo + + for i := 0; i < 5; i++ { + newFoo := foo.NewFoo( + "bar"+strconv.Itoa(i), + i, + ) + + multipleFoos = append(multipleFoos, newFoo) + } + + foo.AddFoos(multipleFoos) +} + +``` + +3. Calling methods on exported variables: + +```go +package main + +import "gno.land/r/docs/examples/run/foo" + +func main() { + println(foo.MainFoo.String()) +} +``` + +Finally, we can call methods that are on top-level objects in case they exist, +which is not currently possible with the `Call` message. + +## Making an airgapped transaction + +`gnokey` provides a way to create a transaction, sign it, and later +broadcast it to a chain in the most secure fashion. This approach, while more +complicated than the standard approach shown [in a previous tutorial](making-transactions.md), +grants full control and provides [airgap](https://en.wikipedia.org/wiki/Air_gap_(networking)) +support. + +By separating the signing and the broadcasting steps of submitting a transaction, +users can make sure that the signing happens in a secure, offline environment, +keeping private keys away from possible exposure to attacks coming from the +internet. + +The intended purpose of this functionality is to provide maximum security when +signing and broadcasting a transaction. In practice, this procedure should take +place on two separate machines controlled by the holder of the keys, one with +access to the internet (`Machine A`), and the other one without (`Machine B`), +with the separation of steps as follows: +1. `Machine A`: Fetch account information from the chain +2. `Machine B`: Create an unsigned transaction locally +3. `Machine B`: Sign the transaction +4. `Machine A`: Broadcast the transaction + +## 1. Fetching account information from the chain + +First, we need to fetch data for the account we are using to sign the transaction, +using the [auth/accounts](querying-a-network.md#authaccounts) query: + +```bash +gnokey query auth/accounts/ -remote "https://rpc.gno.land:443" +``` + +We need to extract the account number and sequence from the output: + +```bash +height: 0 +data: { + "BaseAccount": { + "address": "g1zzqd6phlfx0a809vhmykg5c6m44ap9756s7cjj", + "coins": "10000000ugnot", + "public_key": null, + "account_number": "468", + "sequence": "0" + } +} +``` + +In this case, the account number is `468`, and the sequence (nonce) is `0`. We +will need these values to sign the transaction later. These pieces of information +are crucial during the signing process, as they are included in the signature +of the transaction, preventing replay attacks. + +## 2. Creating an unsigned transaction locally + +To create the transaction you want, you can use the [`call` API](making-transactions.md#call), +without the `-broadcast` flag, while redirecting the output to a local file: + +```bash +gnokey maketx call \ +-pkgpath "gno.land/r/demo/userbook" \ +-func "SignUp" \ +-gas-fee 1000000ugnot \ +-gas-wanted 2000000 \ +mykey > userbook.tx +``` + +This will create a `userbook.tx` file with a null `signature` field. +Now we are ready to sign the transaction. + +## 3. Signing the transaction + +To add a signature to the transaction, we can use the `gnokey sign` subcommand. +To sign, we must set the correct flags for the subcommand: +- `-tx-path` - path to the transaction file to sign, in our case, `userbook.tx` +- `-chainid` - id of the chain to sign for +- `-account-number` - number of the account fetched previously +- `-account-sequence` - sequence of the account fetched previously + +```bash +gnokey sign \ +-tx-path userbook.tx \ +-chainid "portal-loop" \ +-account-number 468 \ +-account-sequence 0 \ +mykey +``` + +After inputting the correct values, `gnokey` will ask for the password to decrypt +the key pair. Once we input the password, we should receive the message that the +signing was completed. If we open the `userbook.tx` file, we will be able to see +that the signature field has been populated. + +We are now ready to broadcast this transaction to the chain. + +## 4. Broadcasting the transaction + +To broadcast the signed transaction to the chain, we can use the `gnokey broadcast` +subcommand, giving it the path to the signed transaction: + +```bash +gnokey broadcast -remote "https://rpc.gno.land:443" userbook.tx +``` + +In this case, we do not need to specify a key pair, as the transaction has already +been signed in a previous step and `gnokey` is only sending it to the RPC endpoint. + +## Verifying a transaction's signature + +To verify a transaction's signature is correct, you can use the `gnokey verify` +subcommand. We can provide the path to the transaction document using the `-docpath` +flag, provide the key we signed the transaction with, and the signature itself. +Make sure the signature is in the `hex` format. + +```bash +gnokey verify -docpath userbook.tx mykey +``` + +# Querying a gno.land network + +gno.land and `gnokey` support ABCI queries. Using ABCI queries, you can query the state of +a gno.land network without spending any gas. All queries need to be pointed towards +a specific remote address from which the state will be retrieved. + +To send ABCI queries, you can use the `gnokey query` subcommand, and provide it +with the appropriate query. The `query` subcommand allows us to send different +types of queries to a gno.land network. + +Below is a list of queries a user can make with `gnokey`: +- `auth/accounts/{ADDRESS}` - returns information about an account +- `bank/balances/{ADDRESS}` - returns balances of an account +- `vm/qfuncs` - returns the exported functions for a given pkgpath +- `vm/qfile` - returns package contents for a given pkgpath +- `vm/qdoc` - Returns the JSON of the doc for a given pkgpath, suitable for printing +- `vm/qeval` - evaluates an expression in read-only mode on and returns the results +- `vm/qrender` - shorthand for evaluating `vm/qeval Render("")` for a given pkgpath + +Let's see how we can use them. + +## `auth/accounts` + +We can obtain information about a specific address using this subquery. To call it, +we can run the following command: + +```bash +gnokey query auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -remote https://rpc.gno.land:443 +``` + +With this, we are asking the Portal Loop network to deliver information about the +specified address. If everything went correctly, we should get output similar to the following: + +```bash +height: 0 +data: { + "BaseAccount": { + "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "coins": "227984898927ugnot", + "public_key": { + "@type": "/tm.PubKeySecp256k1", + "value": "A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y" + }, + "account_number": "0", + "sequence": "12" + } +} +``` + +The return data will contain the following fields: +- `height` - the height at which the query was executed. This is currently not + supported and is `0` by default. +- `data` - contains the result of the query. + +The `data` field returns a `BaseAccount`, which is the main struct used in Tendermint2 +to hold account data. It contains the following information: +- `address` - the address of the account +- `coins` - the list of coins the account owns +- `public_key` - the TM2 public key of the account, from which the address is derived +- `account_number` - a unique identifier for the account on the gno.land chain +- `sequence` - a nonce, used for protection against replay attacks + +## `bank/balances` + +With this query, we can fetch [coin](../resources/gno-stdlibs.md#coin) balances +of a specific account. To call it, we can run the following command: + +```bash +gnokey query bank/balances/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -remote https://rpc.gno.land:443 +``` + +If everything went correctly, we should get an output similar to the following: + +```bash +height: 0 +data: "227984898927ugnot" +``` + +The data field will contain the coins the address owns. + +## `vm/qfuncs` + +Using the `vm/qfuncs` query, we can fetch exported functions from a specific package +path. To specify the path we want to query, we can use the `-data` flag: + +```bash +gnokey query vm/qfuncs --data "gno.land/r/demo/wugnot" -remote https://rpc.gno.land:443 +``` + +The output is a string containing all exported functions for the `wugnot` realm: + +```json +height: 0 +data: [ + { + "FuncName": "Deposit", + "Params": null, + "Results": null + }, + { + "FuncName": "Withdraw", + "Params": [ + { + "Name": "amount", + "Type": "uint64", + "Value": "" + } + ], + "Results": null + }, + // other functions +] +``` + +## `vm/qfile` + +With the `vm/qfile` query, we can fetch files and their content found on a +specific package path. To specify the path we want to query, we can use the +`-data` flag: + +```bash +gnokey query vm/qfile -data "gno.land/r/demo/wugnot" -remote https://rpc.gno.land:443 +``` + +If the `-data` field contains only the package path, the output is a list of all +files found within the `wugnot` realm: + +```bash +height: 0 +data: gno.mod +wugnot.gno +z0_filetest.gno +``` + +If the `-data` field also specifies a file name after the path, the source code +of the file will be retrieved: + +```bash +gnokey query vm/qfile -data "gno.land/r/demo/wugnot/wugnot.gno" -remote https://rpc.gno.land:443 +``` + +Output: +```bash +height: 0 +data: package wugnot + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + "gno.land/r/demo/users" +) + +var ( + banker *grc20.Banker = grc20.NewBanker("wrapped GNOT", "wugnot", 0) + Token = banker.Token() +) + +const ( + ugnotMinDeposit uint64 = 1000 + wugnotMinDeposit uint64 = 1 +) +... +``` + +## `vm/qdoc` + +Using the `vm/qdoc` query, we can fetch the docs, for functions, types and variables from a specific +package path. To specify the path we want to query, we can use the `-data` flag: + +```bash +gnokey query vm/qdoc --data "gno.land/r/gnoland/valopers/v2" -remote https://rpc.gno.land:443 +``` + +The output is a JSON string containing doc strings of the package, functions, etc., including comments for `valopers` realm: + +```json +height: 0 +data: { + "package_path": "gno.land/r/gnoland/valopers/v2", + "package_line": "package valopers // import \"valopers\"", + "package_doc": "Package valopers is designed around the permissionless lifecycle of valoper profiles. It also includes parts designed for govdao to propose valset changes based on registered valopers.\n", + "values": [ + { + "name": "valopers", + "doc": "// Address -> Valoper\n", + "type": "*avl.Tree" + } + // other values + ], + "funcs": [ + { + "type": "", + "name": "GetByAddr", + "signature": "func GetByAddr(address std.Address) Valoper", + "doc": "GetByAddr fetches the valoper using the address, if present\n", + "params": [ + { + "Name": "address", + "Type": "std.Address" + } + ], + "results": [ + { + "Name": "", + "Type": "Valoper" + } + ] + } + // other funcs + { + "type": "Valoper", + "name": "Render", + "signature": "func (v Valoper) Render() string", + "doc": "Render renders a single valoper with their information\n", + "params": [], + "results": [ + { + "Name": "", + "Type": "string" + } + ] + } + // other methods (in this case of the Valoper type) + ], + "types": [ + { + "name": "Valoper", + "signature": "type Valoper struct {\n\tName string // the display name of the valoper\n\tMoniker string // the moniker of the valoper\n\tDescription string // the description of the valoper\n\n\tAddress std.Address // The bech32 gno address of the validator\n\tPubKey string // the bech32 public key of the validator\n\tP2PAddresses []string // the publicly reachable P2P addresses of the validator\n\tActive bool // flag indicating if the valoper is active\n}", + "doc": "Valoper represents a validator operator profile\n" + } + ] +} +``` + +## `vm/qeval` + +`vm/qeval` allows us to evaluate a call to an exported function without using gas, +in read-only mode. For example: + +```bash +gnokey query vm/qeval -remote https://rpc.gno.land:443 -data "gno.land/r/demo/wugnot.BalanceOf(\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\")" +``` + +This command will return the `wugnot` balance of the above address without using gas. +Properly escaping quotation marks for string arguments is currently required. + +Currently, `vm/qeval` only supports primitive types in expressions. + +## `vm/qrender` + +`vm/qrender` is an alias for executing `vm/qeval` on the `Render("")` function. +We can use it like this: + +```bash +gnokey query vm/qrender --data "gno.land/r/demo/wugnot:" -remote https://rpc.gno.land:443 +``` + +Running this command will display the current `Render()` output of the WUGNOT +realm, which is also displayed by default on the [realm's page](https://gno.land/r/demo/wugnot): + +```bash +height: 0 +data: # wrapped GNOT ($wugnot) + +* **Decimals**: 0 +* **Total supply**: 5012404 +* **Known accounts**: 2 +``` + +:::info Specifying a path to `Render()` + +To call the `vm/qrender` query with a specific path, use the `:` syntax. +For example, the `wugnot` realm provides a way to display the balance of a specific +address in its `Render()` function. We can fetch the balance of an account by +providing the following custom pattern to the `wugnot` realm: + +```bash +gnokey query vm/qrender --data "gno.land/r/demo/wugnot:balance/g125em6arxsnj49vx35f0n0z34putv5ty3376fg5" -remote https://rpc.gno.land:443 +``` + +To see how this was achieved, check out `wugnot`'s `Render()` function. +::: + +### Gas parameters + +When using `gnokey` to send transactions, you'll need to specify gas parameters: + +```bash +gnokey maketx call \ + --pkgpath "gno.land/r/demo/boards" \ + --func "CreateBoard" \ + --args "MyBoard" "Board description" \ + --gas-fee 1000000ugnot \ + --gas-wanted 2000000 \ + --remote https://rpc.gno.land:443 \ + --chainid portal-loop \ + YOUR_KEY_NAME +``` + +For detailed information about gas fees, including recommended values and optimization strategies, see the [Gas Fees documentation](../resources/gas-fees.md). + +## Conclusion + +That's it! 🎉 + +In this tutorial, you've learned to use `gnokey` to query a gno.land +network. + diff --git a/docs/users/power-users.md b/docs/users/power-users.md new file mode 100644 index 00000000000..7eeaa201730 --- /dev/null +++ b/docs/users/power-users.md @@ -0,0 +1,61 @@ +# Becoming a gno.land Power User + +Ready to take your gno.land journey to the next level? This guide collects +resources, tools, and communities for users who want to dive deeper into the +gno.land ecosystem. + +## Establishing Your Identity + +To get the most out of gno.land, consider registering a unique username: + +1. A username provides a more human-readable identity than a blockchain address +2. It gives you exclusive rights to deploy code under that namespace +3. It improves your experience across various dApps in the ecosystem + +For detailed instructions on registering a username and understanding +namespaces, see the [Users and Teams](../resources/users-and-teams.md) +documentation. + +## Community & Social Networks + +Stay updated and connected with the gno.land community: + +- [Twitter/X](https://twitter.com/_gnoland) - Latest announcements and updates +- [GitHub](https://github.com/gnolang) - Repositories for all gno.land projects +- [Forum](https://gno.land/r/demo/boards/) - Long-form discussions and proposals + +## Tools + +Power up your gno.land journey with these tools: + +- [Gno Studio](https://gno.studio/) - Web IDE for Gno development + - [Gno Studio Connect](https://gno.studio/connect) - Interact with any gno.land app with a web wallet +- [Gno Playground](https://play.gno.land/) - Try Gno code in your browser +- [Faucet Hub](https://faucet.gno.land/) - Get testnet tokens + +## Monitoring & Analytics + +- [Block Explorer](https://gnoscan.io/) - Transaction history and block details +- [Network Status](https://status.gnoteam.com/) - Health and status of gno.land networks + +## Educational Resources + +- [Game of Realms](https://github.com/gnolang/game-of-realms) - Learn through gamified challenges +- [Awesome Gno](https://github.com/gnoverse/awesome-gno) - Curated list of gno.land resources +- [Workshops Repository](https://github.com/gnolang/workshops) - Past workshop materials + +## Transitioning from User to Builder + +Ready to contribute? Here are pathways to level up your involvement: + +1. **Building dApps** - Create your own applications on gno.land +2. **Contributing to Core** - Help improve the Gno language and tools +3. **Creating Tools** - Build supporting infrastructure for the ecosystem +4. **Documentation** - Help improve these docs and educational content +5. **Community Building** - Organize events or create content + +Check out the [Become a Gnome](../builders/become-a-gnome.md) guide for more +details on contributing to the ecosystem. + +Remember, the best power users eventually become builders. As you grow more +comfortable with gno.land, consider how you can help shape its future! diff --git a/docs/users/third-party-wallets.md b/docs/users/third-party-wallets.md new file mode 100644 index 00000000000..19db69240fd --- /dev/null +++ b/docs/users/third-party-wallets.md @@ -0,0 +1,43 @@ +# Using an External Wallet on gno.land + +While gno.land provides its own command-line wallet (`gnokey`), you may prefer +using a more user-friendly browser extension or mobile wallet. This page covers +available third-party wallets that support gno.land networks. + +## GnoConnect Integration + +gno.land uses GnoConnect, a protocol that allows external wallets to integrate +with gno-powered blockchains. This provides a standardized way for wallets to: + +- Connect to gno.land networks +- Sign and broadcast transactions +- Interact with smart contracts (realms) +- Manage your GNOT and tokens + +## Compatible Wallets + +- [Adena](https://adena.app) is a browser extension wallet specifically designed + for gno.land. It's the most feature-complete wallet currently available. + +## Choosing a Wallet + +When selecting a wallet, consider these factors: + +- **Security:** Has the wallet been audited? What security practices do they follow? +- **Features:** Does it support all the functions you need? +- **User Experience:** Is it easy to use and understand? +- **Maintenance:** Is it actively maintained and updated? + +For beginners, we recommend starting with Adena as it provides the most complete +support for gno.land features and has undergone security audits. + +## Getting Started + +To get started with an external wallet: + +1. Install your chosen wallet extension or app +2. Create a new account or import an existing one +3. Configure the wallet to connect to your desired gno.land network +4. Get some testnet GNOT from the [Faucet Hub](https://faucet.gno.land) + +You're now ready to interact with gno.land through your wallet! diff --git a/examples/Makefile b/examples/Makefile index cdc73ee6b3a..23a02f6228f 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -22,22 +22,15 @@ GOIMPORTS_FLAGS ?= $(GOFMT_FLAGS) # test suite flags. GOTEST_FLAGS ?= -v -p 1 -timeout=30m -# Official packages (non-overridable): more reliable and tested modules, distinct from the experimentation area. -OFFICIAL_PACKAGES = ./gno.land/p -OFFICIAL_PACKAGES += ./gno.land/r/demo -OFFICIAL_PACKAGES += ./gno.land/r/gnoland -OFFICIAL_PACKAGES += ./gno.land/r/sys -OFFICIAL_PACKAGES += ./gno.land/r/gov - ######################################## # Dev tools .PHONY: transpile transpile: - go run ../gnovm/cmd/gno transpile -v . + go run ../gnovm/cmd/gno tool transpile -v . .PHONY: build build: - go run ../gnovm/cmd/gno transpile -v --gobuild . + go run ../gnovm/cmd/gno tool transpile -v --gobuild . .PHONY: test test: @@ -45,7 +38,7 @@ test: .PHONY: lint lint: - go run ../gnovm/cmd/gno lint -v $(OFFICIAL_PACKAGES) + go run ../gnovm/cmd/gno tool lint -v . .PHONY: test.sync test.sync: @@ -63,3 +56,8 @@ fmt: .PHONY: tidy tidy: go run github.com/gnolang/gno/gnovm/cmd/gno mod tidy -v --recursive + +.PHONY: generate +generate: + go generate ./... + $(MAKE) fmt diff --git a/examples/gno.land/p/agherasie/forms/create.gno b/examples/gno.land/p/agherasie/forms/create.gno new file mode 100644 index 00000000000..f37c3ba24ad --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/create.gno @@ -0,0 +1,80 @@ +package forms + +import ( + "std" + "time" + + "gno.land/p/demo/json" +) + +const dateFormat = "2006-01-02T15:04:05Z" + +func CreateField(label string, fieldType string, required bool) Field { + return Field{ + Label: label, + FieldType: fieldType, + Required: required, + } +} + +// CreateForm creates a new form with the given parameters +func (db *FormDB) CreateForm(title string, description string, openAt string, closeAt string, data string) (string, error) { + // Parsing the dates + var parsedOpenTime, parsedCloseTime time.Time + + if openAt != "" { + var err error + parsedOpenTime, err = time.Parse(dateFormat, openAt) + if err != nil { + return "", errInvalidDate + } + } + + if closeAt != "" { + var err error + parsedCloseTime, err = time.Parse(dateFormat, closeAt) + if err != nil { + return "", errInvalidDate + } + } + + // Parsing the json submission + node, err := json.Unmarshal([]byte(data)) + if err != nil { + return "", errInvalidJson + } + + fieldsCount := node.Size() + fields := make([]Field, fieldsCount) + + // Parsing the json submission to create the gno data structures + for i := 0; i < fieldsCount; i++ { + field := node.MustIndex(i) + + fields[i] = CreateField( + field.MustKey("label").MustString(), + field.MustKey("fieldType").MustString(), + field.MustKey("required").MustBool(), + ) + } + + // Generating the form ID + id := db.IDCounter.Next().String() + + // Creating the form + form := Form{ + ID: id, + Owner: std.PreviousRealm().Address(), + Title: title, + Description: description, + CreatedAt: time.Now(), + openAt: parsedOpenTime, + closeAt: parsedCloseTime, + Fields: fields, + } + + // Adding the form to the database + db.Forms = append(db.Forms, &form) + + return id, nil +} diff --git a/examples/gno.land/p/agherasie/forms/create_test.gno b/examples/gno.land/p/agherasie/forms/create_test.gno new file mode 100644 index 00000000000..601f9e04535 --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/create_test.gno @@ -0,0 +1,70 @@ +package forms + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" +) + +func TestCreateForm(t *testing.T) { + alice := testutils.TestAddress("alice") + std.TestSetOriginCaller(alice) + db := NewDB() + title := "Simple Form" + description := "This is a form" + openAt := "2021-01-01T00:00:00Z" + closeAt := "2021-01-02T00:00:00Z" + data := `[ + { + "label": "Name", + "fieldType": "string", + "required": true + }, + { + "label": "Age", + "fieldType": "number", + "required": false + }, + { + "label": "Is this a test?", + "fieldType": "boolean", + "required": false + }, + { + "label": "Favorite Food", + "fieldType": "['Pizza', 'Schnitzel', 'Burger']", + "required": true + }, + { + "label": "Favorite Foods", + "fieldType": "{'Pizza', 'Schnitzel', 'Burger'}", + "required": true + } + ]` + + urequire.NotPanics(t, func() { + id, err := db.CreateForm(title, description, openAt, closeAt, data) + if err != nil { + panic(err) + } + urequire.True(t, id != "", "Form ID is empty") + + form, err := db.GetForm(id) + if err != nil { + panic(err) + } + + urequire.True(t, form.ID == id, "Form ID is not correct") + urequire.True(t, form.Owner == alice, "Owner is not correct") + urequire.True(t, form.Title == title, "Title is not correct") + urequire.True(t, form.Description == description, "Description is not correct") + urequire.True(t, len(form.Fields) == 5, "Not enough fields were provided") + urequire.True(t, form.Fields[0].Label == "Name", "Field 0 label is not correct") + urequire.True(t, form.Fields[0].FieldType == "string", "Field 0 type is not correct") + urequire.True(t, form.Fields[0].Required == true, "Field 0 required is not correct") + urequire.True(t, form.Fields[1].Label == "Age", "Field 1 label is not correct") + urequire.True(t, form.Fields[1].FieldType == "number", "Field 1 type is not correct") + }) +} diff --git a/examples/gno.land/p/agherasie/forms/doc.gno b/examples/gno.land/p/agherasie/forms/doc.gno new file mode 100644 index 00000000000..dc7f5d08ac7 --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/doc.gno @@ -0,0 +1,25 @@ +// # Gno forms + +// gno-forms is a package which demonstrates a form editing and sharing application in gno + +// ## Features +// - **Form Creation**: Create new forms with specified titles, descriptions, and fields. +// - **Form Submission**: Submit answers to forms. +// - **Form Retrieval**: Retrieve existing forms and their submissions. +// - **Form Deadline**: Set a precise time range during which a form can be interacted with. + +// ## Field Types +// The system supports the following field types: + +// | type | example | +// |--------------|-------------------------------------------------------------------------------------------------| +// | string | `{"label": "Name", "fieldType": "string", "required": true}` | +// | number | `{"label": "Age", "fieldType": "number", "required": true}` | +// | boolean | `{"label": "Is Student?", "fieldType": "boolean", "required": false}` | +// | choice | `{"label": "Favorite Food", "fieldType": "['Pizza', 'Schnitzel', 'Burger']", "required": true}` | +// | multi-choice | `{"label": "Hobbies", "fieldType": "{'Reading', 'Swimming', 'Gaming'}", "required": false}` | + +// ## Web-app + +// The external repo where the initial development took place and where you can find the frontend is [here](https://github.com/agherasie/gno-forms). +package forms diff --git a/examples/gno.land/p/agherasie/forms/errors.gno b/examples/gno.land/p/agherasie/forms/errors.gno new file mode 100644 index 00000000000..e1d0dabd932 --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/errors.gno @@ -0,0 +1,15 @@ +package forms + +import "errors" + +var ( + errNoOpenDate = errors.New("Form has no open date") + errNoCloseDate = errors.New("Form has no close date") + errInvalidJson = errors.New("Invalid JSON") + errInvalidDate = errors.New("Invalid date") + errFormNotFound = errors.New("Form not found") + errAnswerNotFound = errors.New("Answer not found") + errAlreadySubmitted = errors.New("You already submitted this form") + errFormClosed = errors.New("Form is closed") + errInvalidAnswers = errors.New("Invalid answers") +) diff --git a/examples/gno.land/p/agherasie/forms/forms.gno b/examples/gno.land/p/agherasie/forms/forms.gno new file mode 100644 index 00000000000..f9298e9bd66 --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/forms.gno @@ -0,0 +1,132 @@ +package forms + +import ( + "std" + "time" + + "gno.land/p/demo/seqid" +) + +// FieldType examples : +// - string: "string"; +// - number: "number"; +// - boolean: "boolean"; +// - choice: "['Pizza', 'Schnitzel', 'Burger']"; +// - multi-choice: "{'Pizza', 'Schnitzel', 'Burger'}"; +type Field struct { + Label string + FieldType string + Required bool +} + +type Form struct { + ID string + Owner std.Address + Title string + Description string + Fields []Field + CreatedAt time.Time + openAt time.Time + closeAt time.Time +} + +// Answers example : +// - ["Alex", 21, true, 0, [0, 1]] +type Submission struct { + FormID string + Author std.Address + Answers string // json + SubmittedAt time.Time +} + +type FormDB struct { + Forms []*Form + Answers []*Submission + IDCounter seqid.ID +} + +func NewDB() *FormDB { + return &FormDB{ + Forms: make([]*Form, 0), + Answers: make([]*Submission, 0), + } +} + +// This function checks if the form is open by verifying the given dates +// - If a form doesn't have any dates, it's considered open +// - If a form has only an open date, it's considered open if the open date is in the past +// - If a form has only a close date, it's considered open if the close date is in the future +// - If a form has both open and close dates, it's considered open if the current date is between the open and close dates +func (form *Form) IsOpen() bool { + openAt, errOpen := form.OpenAt() + closedAt, errClose := form.CloseAt() + + noOpenDate := errOpen != nil + noCloseDate := errClose != nil + + if noOpenDate && noCloseDate { + return true + } + + if noOpenDate && !noCloseDate { + return time.Now().Before(closedAt) + } + + if !noOpenDate && noCloseDate { + return time.Now().After(openAt) + } + + now := time.Now() + return now.After(openAt) && now.Before(closedAt) +} + +// OpenAt returns the open date of the form if it exists +func (form *Form) OpenAt() (time.Time, error) { + if form.openAt.IsZero() { + return time.Time{}, errNoOpenDate + } + + return form.openAt, nil +} + +// CloseAt returns the close date of the form if it exists +func (form *Form) CloseAt() (time.Time, error) { + if form.closeAt.IsZero() { + return time.Time{}, errNoCloseDate + } + + return form.closeAt, nil +} + +// GetForm returns a form by its ID if it exists +func (db *FormDB) GetForm(id string) (*Form, error) { + for _, form := range db.Forms { + if form.ID == id { + return form, nil + } + } + return nil, errFormNotFound +} + +// GetAnswer returns an answer by its form - and author ids if it exists +func (db *FormDB) GetAnswer(formID string, author std.Address) (*Submission, error) { + for _, answer := range db.Answers { + if answer.FormID == formID && answer.Author.String() == author.String() { + return answer, nil + } + } + return nil, errAnswerNotFound +} + +// GetSubmissionsByFormID returns a list containing the existing form submissions by the form ID +func (db *FormDB) GetSubmissionsByFormID(formID string) []*Submission { + submissions := make([]*Submission, 0) + + for _, answer := range db.Answers { + if answer.FormID == formID { + submissions = append(submissions, answer) + } + } + + return submissions +} diff --git a/examples/gno.land/p/agherasie/forms/forms_json.gno b/examples/gno.land/p/agherasie/forms/forms_json.gno new file mode 100644 index 00000000000..7e7800ba31e --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/forms_json.gno @@ -0,0 +1,79 @@ +package forms + +import ( + "strings" + + "gno.land/p/demo/json" +) + +type FormNodeBuilder struct { + *json.NodeBuilder +} + +type FormArrayBuilder struct { + *json.ArrayBuilder +} + +func (b *FormNodeBuilder) WriteArray(key string, fn func(*FormArrayBuilder)) *FormNodeBuilder { + b.NodeBuilder.WriteArray(key, func(ab *json.ArrayBuilder) { + fn(&FormArrayBuilder{ab}) + }) + return b +} + +func (b *FormNodeBuilder) WriteObject(key string, fn func(*FormNodeBuilder)) *FormNodeBuilder { + b.NodeBuilder.WriteObject(key, func(nb *json.NodeBuilder) { + fn(&FormNodeBuilder{nb}) + }) + return b +} + +func (b *FormArrayBuilder) WriteObject(fn func(*FormNodeBuilder)) *FormArrayBuilder { + b.ArrayBuilder.WriteObject(func(nb *json.NodeBuilder) { + fn(&FormNodeBuilder{nb}) + }) + return b +} + +func (b *FormNodeBuilder) WriteFormFields(key string, fields []Field) *FormNodeBuilder { + b.WriteArray(key, func(builder *FormArrayBuilder) { + for _, field := range fields { + builder.WriteObject(func(builder *FormNodeBuilder) { + builder.WriteString("label", field.Label). + WriteString("fieldType", field.FieldType). + WriteBool("required", field.Required) + }) + } + }) + return b +} + +func (b *FormNodeBuilder) WriteFormSubmission(key string, submission *Submission) *FormNodeBuilder { + b.WriteObject(key, func(builder *FormNodeBuilder) { + builder.WriteString("submittedAt", submission.SubmittedAt.Format("2006-01-02 15:04:05")). + WriteString("answers", strings.ReplaceAll(submission.Answers, "\"", "'")) + }) + return b +} + +func (b *FormNodeBuilder) WriteForm(key string, value *Form) *FormNodeBuilder { + b.WriteString("id", value.ID). + WriteString("owner", value.Owner.String()). + WriteString("title", value.Title). + WriteString("description", value.Description). + WriteString("createdAt", value.CreatedAt.Format("2006-01-02 15:04:05")) + b.WriteFormFields("fields", value.Fields) + return b +} + +func (b *FormArrayBuilder) WriteForm(key string, value *Form) *FormArrayBuilder { + b.WriteObject(func(builder *FormNodeBuilder) { + builder.WriteString("id", value.ID). + WriteString("owner", value.Owner.String()). + WriteString("title", value.Title). + WriteString("description", value.Description). + WriteString("createdAt", value.CreatedAt.Format("2006-01-02 15:04:05")) + builder.WriteFormFields("fields", value.Fields) + }) + return b +} diff --git a/examples/gno.land/p/agherasie/forms/gno.mod b/examples/gno.land/p/agherasie/forms/gno.mod new file mode 100644 index 00000000000..c0a3d7ef729 --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/gno.mod @@ -0,0 +1 @@ +module gno.land/p/agherasie/forms diff --git a/examples/gno.land/p/agherasie/forms/submit.gno b/examples/gno.land/p/agherasie/forms/submit.gno new file mode 100644 index 00000000000..249fc8b335a --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/submit.gno @@ -0,0 +1,40 @@ +package forms + +import ( + "std" + "time" +) + +// This function allows to submit a form +func (db *FormDB) SubmitForm(formID string, answers string) { + // Check if form exists + form, err := db.GetForm(formID) + if err != nil { + panic(err) + } + + // Check if form was already submitted by this user + previousAnswer, err := db.GetAnswer(formID, std.PreviousRealm().Address()) + if previousAnswer != nil { + panic(errAlreadySubmitted) + } + + // Check time restrictions + if !form.IsOpen() { + panic(errFormClosed) + } + + // Check if answers are formatted correctly + if ValidateAnswers(answers, form.Fields) == false { + panic(errInvalidAnswers) + } + + // Save answers + answer := Submission{ + FormID: formID, + Answers: answers, + Author: std.PreviousRealm().Address(), + SubmittedAt: time.Now(), + } + db.Answers = append(db.Answers, &answer) +} diff --git a/examples/gno.land/p/agherasie/forms/submit_test.gno b/examples/gno.land/p/agherasie/forms/submit_test.gno new file mode 100644 index 00000000000..e5125acbae2 --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/submit_test.gno @@ -0,0 +1,92 @@ +package forms + +import ( + "testing" + "time" + + "gno.land/p/demo/urequire" +) + +func TestAnswerForm(t *testing.T) { + db := NewDB() + + data := `[ + { + "label": "Name", + "fieldType": "string", + "required": true + }, + { + "label": "Age", + "fieldType": "number", + "required": false + }, + { + "label": "Is this a test?", + "fieldType": "boolean", + "required": false + }, + { + "label": "Favorite Food", + "fieldType": "[Pizza|Schnitzel|Burger]", + "required": true + }, + { + "label": "Favorite Foods", + "fieldType": "{Pizza|Schnitzel|Burger}", + "required": true + } + ]` + + formID, err := db.CreateForm("Test Form", "Test Description", "", "", data) + if err != nil { + t.Error(err) + } + answers := `["Alex", 21, true, 0, [0, 1]]` + db.SubmitForm(formID, answers) + + urequire.True(t, len(db.Answers) == 1, "Expected 1 answer, got", string(len(db.Answers))) + urequire.True(t, db.Answers[0].FormID == formID, "Expected form ID", formID, "got", db.Answers[0].FormID) + urequire.True(t, db.Answers[0].Answers == answers, "Expected answers", answers, "got", db.Answers[0].Answers) +} + +func TestAnswerFormDates(t *testing.T) { + db := NewDB() + + now := time.Now() + tomorrow := now.AddDate(0, 0, 1).Format("2006-01-02T15:04:05Z") + yesterday := now.AddDate(0, 0, -1).Format("2006-01-02T15:04:05Z") + + data := `[ + { + "label": "Name", + "fieldType": "string", + "required": true + } + ]` + answers := `["Test"]` + + urequire.PanicsWithMessage(t, "Form is closed", func() { + formID, err := db.CreateForm("Test Form", "Test Description", tomorrow, "", data) + if err != nil { + t.Error(err) + } + db.SubmitForm(formID, answers) + }) + + urequire.PanicsWithMessage(t, "Form is closed", func() { + formID, err := db.CreateForm("Test Form", "Test Description", "", yesterday, data) + if err != nil { + t.Error(err) + } + db.SubmitForm(formID, answers) + }) + + urequire.NotPanics(t, func() { + formID, err := db.CreateForm("Test Form", "Test Description", yesterday, tomorrow, data) + if err != nil { + panic(err) + } + db.SubmitForm(formID, answers) + }) +} diff --git a/examples/gno.land/p/agherasie/forms/validate.gno b/examples/gno.land/p/agherasie/forms/validate.gno new file mode 100644 index 00000000000..be5bddf18a1 --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/validate.gno @@ -0,0 +1,148 @@ +package forms + +import ( + "strings" + + "gno.land/p/demo/json" +) + +func validateBooleanField(node *json.Node, field Field) bool { + if node.IsBool() == false { + return false + } + + answer, err := node.GetBool() + if err != nil { + return false + } + + // If the field is required, checkbox must be checked + if field.Required == true && answer == false { + return false + } + + return true +} + +func validateStringField(node *json.Node, field Field) bool { + if node.IsString() == false { + return false + } + + answer, err := node.GetString() + if err != nil { + return false + } + + // If the field is required, the answer must not be empty + if field.Required == true && answer == "" { + return false + } + + return true +} + +func validateNumberField(node *json.Node, field Field) bool { + if node.IsNumber() == false { + return false + } + + _, err := node.GetNumeric() + if err != nil { + return false + } + + return true +} + +func validateMultiChoiceField(node *json.Node, field Field) bool { + choices := strings.Split(field.FieldType[1:len(field.FieldType)-1], "|") + + if node.IsArray() == false { + return false + } + + if field.Required == true && node.Size() == 0 { + return false + } + + if node.Size() > len(choices) { + return false + } + + for i := 0; i < node.Size(); i++ { + choiceNode, err := node.GetIndex(i) + if err != nil { + return false + } + + choiceIdx := choiceNode.MustNumeric() + if choiceIdx < 0 || int(choiceIdx) >= len(choices) { + return false + } + } + + return true +} + +func validateChoiceField(node *json.Node, field Field) bool { + choices := strings.Split(field.FieldType[1:len(field.FieldType)-1], "|") + + if node.IsNumber() == false { + return false + } + + choiceIdx := node.MustNumeric() + if choiceIdx < 0 || int(choiceIdx) >= len(choices) { + return false + } + + return true +} + +func ValidateAnswer(answer *json.Node, field Field) bool { + if field.FieldType == "boolean" { + return validateBooleanField(answer, field) + } else if field.FieldType == "string" { + return validateStringField(answer, field) + } else if field.FieldType == "number" { + return validateNumberField(answer, field) + } else if strings.HasPrefix(field.FieldType, "{") && strings.HasSuffix(field.FieldType, "}") { + return validateMultiChoiceField(answer, field) + } else if strings.HasPrefix(field.FieldType, "[") && strings.HasSuffix(field.FieldType, "]") { + return validateChoiceField(answer, field) + } + + return false +} + +// ValidateAnswers checks if the given answers are valid for the given fields +func ValidateAnswers(answers string, fields []Field) bool { + unmarshalled, err := json.Unmarshal([]byte(answers)) + if err != nil { + return false + } + + // If the number of answers is different from the number of fields, it's invalid + if len(fields) != unmarshalled.Size() { + return false + } + + for i, field := range fields { + answer, err := unmarshalled.GetIndex(i) + if err != nil { + return false + } + + // If the answer is empty and the field is not required, it's valid + if answer.IsNull() && !field.Required { + return true + } + + if !ValidateAnswer(answer, field) { + return false + } + } + + return true +} diff --git a/examples/gno.land/p/agherasie/forms/validate_test.gno b/examples/gno.land/p/agherasie/forms/validate_test.gno new file mode 100644 index 00000000000..952cb6d96d4 --- /dev/null +++ b/examples/gno.land/p/agherasie/forms/validate_test.gno @@ -0,0 +1,140 @@ +package forms + +import ( + "testing" +) + +func TestAnswerFormInvalidForm(t *testing.T) { + db := NewDB() + + dataAllTypes := `[ + { + "label": "Name", + "fieldType": "string", + "required": true + }, + { + "label": "Age", + "fieldType": "number", + "required": false + }, + { + "label": "Is this a test?", + "fieldType": "boolean", + "required": false + }, + { + "label": "Favorite Food", + "fieldType": "[Pizza|Schnitzel|Burger]", + "required": true + }, + { + "label": "Favorite Foods", + "fieldType": "{Pizza|Schnitzel|Burger}", + "required": true + } + ]` + dataOneRequiredText := `[ + { + "label": "Name", + "fieldType": "string", + "required": true + } + ]` + + tests := []struct { + name string + answer string + expectPanic bool + data string + }{ + { + name: "correct", + answer: `["Alex", 21, true, 0, [0, 1]]`, + expectPanic: false, + data: dataAllTypes, + }, + { + name: "invalid string", + answer: `[0, 21, true, 0, [0, 1]`, + expectPanic: true, + data: dataAllTypes, + }, + { + name: "invalid number", + answer: `["Alex", "21", true, 0, [0, 1]]`, + expectPanic: true, + data: dataAllTypes, + }, + { + name: "invalid boolean", + answer: `["Alex", 21, 1, 0, [0, 1]]`, + expectPanic: true, + data: dataAllTypes, + }, + { + name: "invalid choice", + answer: `["Alex", 21, true, 10, [0, 1]]`, + expectPanic: true, + data: dataAllTypes, + }, + { + name: "invalid multi-choice 1", + answer: `["Alex", 21, true, 0, [0, 1, 2, 3, 4, 5]]`, + expectPanic: true, + data: dataAllTypes, + }, + { + name: "invalid multi-choice 2", + answer: `["Alex", 21, true, 0, [5]]`, + expectPanic: true, + data: dataAllTypes, + }, + { + name: "invalid multi-choice 3", + answer: `["Alex", 21, true, 0, 0]`, + expectPanic: true, + data: dataAllTypes, + }, + { + name: "required string", + answer: `["", 21, true, 0, [0, 1]]`, + expectPanic: true, + data: dataAllTypes, + }, + { + name: "unrequired number", + answer: `["Alex", null, true, 0, [0, 1]]`, + expectPanic: false, + data: dataAllTypes, + }, + { + name: "correct one field", + answer: `["Alex"]`, + expectPanic: false, + data: dataOneRequiredText, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + formID, err := db.CreateForm("Test Form", "Test Description", "", "", tt.data) + if err != nil { + t.Error(err) + } + + defer func() { + if r := recover(); r != nil { + if tt.expectPanic { + return + } + t.Errorf("%s panic occurred when not expected: %v", tt.name, r) + } else if tt.expectPanic { + t.Errorf("%s expected panic but didn't occur", tt.name) + } + }() + + db.SubmitForm(formID, tt.answer) + }) + } +} diff --git a/examples/gno.land/p/demo/avl/list/list.gno b/examples/gno.land/p/demo/avl/list/list.gno index 594f5fa2a1f..8688c69648e 100644 --- a/examples/gno.land/p/demo/avl/list/list.gno +++ b/examples/gno.land/p/demo/avl/list/list.gno @@ -19,7 +19,7 @@ // l.Delete(0) // removes first element // // // Iterate over elements -// l.ForEach(func(index int, value interface{}) bool { +// l.ForEach(func(index int, value any) bool { // ufmt.Printf("index %d: %v\n", index, value) // return false // continue iteration // }) @@ -44,12 +44,12 @@ import ( // IList defines the interface for list operations type IList interface { Len() int - Append(values ...interface{}) - Get(index int) interface{} - Set(index int, value interface{}) bool - Delete(index int) (interface{}, bool) - Slice(startIndex, endIndex int) []interface{} - ForEach(fn func(index int, value interface{}) bool) + Append(values ...any) + Get(index int) any + Set(index int, value any) bool + Delete(index int) (any, bool) + Slice(startIndex, endIndex int) []any + ForEach(fn func(index int, value any) bool) Clone() *List DeleteRange(startIndex, endIndex int) int } @@ -82,7 +82,7 @@ func (l *List) Len() int { // l.Append(1) // adds single value // l.Append(2, 3, 4) // adds multiple values // println(l.Len()) // Output: 4 -func (l *List) Append(values ...interface{}) { +func (l *List) Append(values ...any) { for _, v := range values { l.tree.Set(l.idGen.Next().String(), v) } @@ -98,7 +98,7 @@ func (l *List) Append(values ...interface{}) { // println(l.Get(1)) // Output: 2 // println(l.Get(-1)) // Output: nil // println(l.Get(999)) // Output: nil -func (l *List) Get(index int) interface{} { +func (l *List) Get(index int) any { if index < 0 || index >= l.tree.Size() { return nil } @@ -123,7 +123,7 @@ func (l *List) Get(index int) interface{} { // // l.Set(-1, 5) // invalid index // println(l.Len()) // Output: 4 (list unchanged) -func (l *List) Set(index int, value interface{}) bool { +func (l *List) Set(index int, value any) bool { size := l.tree.Size() // Handle empty list case - only allow index 0 @@ -170,7 +170,7 @@ func (l *List) Set(index int, value interface{}) bool { // // val, ok = l.Delete(-1) // println(val, ok) // Output: nil false -func (l *List) Delete(index int) (interface{}, bool) { +func (l *List) Delete(index int) (any, bool) { size := l.tree.Size() // Always return nil, false for empty list if size == 0 { @@ -202,7 +202,7 @@ func (l *List) Delete(index int) (interface{}, bool) { // println(l.Slice(-1, 2)) // Output: [1 2] // println(l.Slice(3, 999)) // Output: [4 5] // println(l.Slice(3, 2)) // Output: nil -func (l *List) Slice(startIndex, endIndex int) []interface{} { +func (l *List) Slice(startIndex, endIndex int) []any { size := l.tree.Size() // Normalize bounds @@ -217,10 +217,10 @@ func (l *List) Slice(startIndex, endIndex int) []interface{} { } count := endIndex - startIndex - result := make([]interface{}, count) + result := make([]any, count) i := 0 - l.tree.IterateByOffset(startIndex, count, func(_ string, value interface{}) bool { + l.tree.IterateByOffset(startIndex, count, func(_ string, value any) bool { result[i] = value i++ return false @@ -229,13 +229,13 @@ func (l *List) Slice(startIndex, endIndex int) []interface{} { } // ForEach iterates through all elements in the list. -func (l *List) ForEach(fn func(index int, value interface{}) bool) { +func (l *List) ForEach(fn func(index int, value any) bool) { if l.tree.Size() == 0 { return } index := 0 - l.tree.IterateByOffset(0, l.tree.Size(), func(_ string, value interface{}) bool { + l.tree.IterateByOffset(0, l.tree.Size(), func(_ string, value any) bool { result := fn(index, value) index++ return result @@ -265,7 +265,7 @@ func (l *List) Clone() *List { return newList } - l.tree.IterateByOffset(0, size, func(_ string, value interface{}) bool { + l.tree.IterateByOffset(0, size, func(_ string, value any) bool { newList.Append(value) return false }) @@ -300,7 +300,7 @@ func (l *List) DeleteRange(startIndex, endIndex int) int { // Collect keys to delete keysToDelete := make([]string, 0, endIndex-startIndex) - l.tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, _ interface{}) bool { + l.tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, _ any) bool { keysToDelete = append(keysToDelete, key) return false }) diff --git a/examples/gno.land/p/demo/avl/list/list_test.gno b/examples/gno.land/p/demo/avl/list/list_test.gno index 0293692f660..3b79dd9b2eb 100644 --- a/examples/gno.land/p/demo/avl/list/list_test.gno +++ b/examples/gno.land/p/demo/avl/list/list_test.gno @@ -69,7 +69,7 @@ func TestList_Set(t *testing.T) { } // Verify list state hasn't changed after invalid operations - expected := []interface{}{1, 42, 3, 4} + expected := []any{1, 42, 3, 4} for i, want := range expected { if got := l.Get(i); got != want { t.Errorf("index %d = %v; want %v", i, got, want) @@ -107,16 +107,16 @@ func TestList_Slice(t *testing.T) { // Test valid ranges values := l.Slice(1, 4) - expected := []interface{}{2, 3, 4} + expected := []any{2, 3, 4} if !sliceEqual(values, expected) { t.Errorf("Slice(1,4) = %v; want %v", values, expected) } // Test edge cases - if values := l.Slice(-1, 2); !sliceEqual(values, []interface{}{1, 2}) { + if values := l.Slice(-1, 2); !sliceEqual(values, []any{1, 2}) { t.Errorf("Slice(-1,2) = %v; want [1 2]", values) } - if values := l.Slice(3, 10); !sliceEqual(values, []interface{}{4, 5}) { + if values := l.Slice(3, 10); !sliceEqual(values, []any{4, 5}) { t.Errorf("Slice(3,10) = %v; want [4 5]", values) } if values := l.Slice(3, 2); values != nil { @@ -129,7 +129,7 @@ func TestList_ForEach(t *testing.T) { l.Append(1, 2, 3) sum := 0 - l.ForEach(func(index int, value interface{}) bool { + l.ForEach(func(index int, value any) bool { sum += value.(int) return false }) @@ -140,7 +140,7 @@ func TestList_ForEach(t *testing.T) { // Test early termination count := 0 - l.ForEach(func(index int, value interface{}) bool { + l.ForEach(func(index int, value any) bool { count++ return true // stop after first item }) @@ -187,7 +187,7 @@ func TestList_DeleteRange(t *testing.T) { if l.Len() != 2 { t.Errorf("after DeleteRange(1,4) len = %d; want 2", l.Len()) } - expected := []interface{}{1, 5} + expected := []any{1, 5} for i, want := range expected { if got := l.Get(i); got != want { t.Errorf("after DeleteRange(1,4) index %d = %v; want %v", i, got, want) @@ -281,7 +281,7 @@ func TestList_LargeOperations(t *testing.T) { // Test range on large list values := l.Slice(n-3, n) - expected := []interface{}{n - 3, n - 2, n - 1} + expected := []any{n - 3, n - 2, n - 1} if !sliceEqual(values, expected) { t.Errorf("Range(%d,%d) = %v; want %v", n-3, n, values, expected) } @@ -305,7 +305,7 @@ func TestList_ChainedOperations(t *testing.T) { l.Append(4) l.Set(1, 5) - expected := []interface{}{1, 5, 4} + expected := []any{1, 5, 4} for i, want := range expected { if got := l.Get(i); got != want { t.Errorf("index %d = %v; want %v", i, got, want) @@ -320,10 +320,10 @@ func TestList_RangeEdgeCases(t *testing.T) { // Test various edge cases for Range cases := []struct { start, end int - want []interface{} + want []any }{ - {-10, 2, []interface{}{1, 2}}, - {3, 10, []interface{}{4, 5}}, + {-10, 2, []any{1, 2}}, + {3, 10, []any{4, 5}}, {0, 0, nil}, {5, 5, nil}, {4, 3, nil}, @@ -357,7 +357,7 @@ func TestList_IndexConsistency(t *testing.T) { l.Append(8, 9, 10) // [1,6,7,8,9,10] // Verify sequence is continuous - expected := []interface{}{1, 6, 7, 8, 9, 10} + expected := []any{1, 6, 7, 8, 9, 10} for i, want := range expected { if got := l.Get(i); got != want { t.Errorf("index %d = %v; want %v", i, got, want) @@ -376,9 +376,9 @@ func TestList_IndexConsistency(t *testing.T) { } // Verify no gaps in iteration - var iteratedValues []interface{} + var iteratedValues []any var indices []int - l.ForEach(func(index int, value interface{}) bool { + l.ForEach(func(index int, value any) bool { iteratedValues = append(iteratedValues, value) indices = append(indices, index) return false @@ -408,7 +408,7 @@ func TestList_RecursiveSafety(t *testing.T) { // Test deep list traversal found := false - l.ForEach(func(i int, v interface{}) bool { + l.ForEach(func(i int, v any) bool { if str, ok := v.(string); ok { if str == "id2" { found = true @@ -432,7 +432,7 @@ func TestList_RecursiveSafety(t *testing.T) { if !short { // Search for a value var lastFound bool - l.ForEach(func(j int, v interface{}) bool { + l.ForEach(func(j int, v any) bool { if str, ok := v.(string); ok { if str == ufmt.Sprintf("id%d", i+3) { lastFound = true @@ -460,7 +460,7 @@ func TestList_RecursiveSafety(t *testing.T) { } // Helper function to compare slices -func sliceEqual(a, b []interface{}) bool { +func sliceEqual(a, b []any) bool { if len(a) != len(b) { return false } diff --git a/examples/gno.land/p/demo/avl/node.gno b/examples/gno.land/p/demo/avl/node.gno index 7d4ddffff02..3c64ad1347e 100644 --- a/examples/gno.land/p/demo/avl/node.gno +++ b/examples/gno.land/p/demo/avl/node.gno @@ -5,16 +5,16 @@ package avl // Node represents a node in an AVL tree. type Node struct { - key string // key is the unique identifier for the node. - value interface{} // value is the data stored in the node. - height int8 // height is the height of the node in the tree. - size int // size is the number of nodes in the subtree rooted at this node. - leftNode *Node // leftNode is the left child of the node. - rightNode *Node // rightNode is the right child of the node. + key string // key is the unique identifier for the node. + value any // value is the data stored in the node. + height int8 // height is the height of the node in the tree. + size int // size is the number of nodes in the subtree rooted at this node. + leftNode *Node // leftNode is the left child of the node. + rightNode *Node // rightNode is the right child of the node. } // NewNode creates a new node with the given key and value. -func NewNode(key string, value interface{}) *Node { +func NewNode(key string, value any) *Node { return &Node{ key: key, value: value, @@ -42,11 +42,10 @@ func (node *Node) Key() string { } // Value returns the value of the node. -func (node *Node) Value() interface{} { +func (node *Node) Value() any { return node.value } -// _copy creates a copy of the node (excluding value). func (node *Node) _copy() *Node { if node.height == 0 { panic("Why are you copying a value node?") @@ -70,16 +69,18 @@ func (node *Node) Has(key string) (has bool) { } if node.height == 0 { return false + } else { + if key < node.key { + return node.getLeftNode().Has(key) + } else { + return node.getRightNode().Has(key) + } } - if key < node.key { - return node.getLeftNode().Has(key) - } - return node.getRightNode().Has(key) } // Get searches for a node with the given key in the subtree rooted at the node // and returns its index, value, and whether it exists. -func (node *Node) Get(key string) (index int, value interface{}, exists bool) { +func (node *Node) Get(key string) (index int, value any, exists bool) { if node == nil { return 0, nil, false } @@ -87,99 +88,92 @@ func (node *Node) Get(key string) (index int, value interface{}, exists bool) { if node.height == 0 { if node.key == key { return 0, node.value, true - } - if node.key < key { + } else if node.key < key { return 1, nil, false + } else { + return 0, nil, false + } + } else { + if key < node.key { + return node.getLeftNode().Get(key) + } else { + rightNode := node.getRightNode() + index, value, exists = rightNode.Get(key) + index += node.size - rightNode.size + return index, value, exists } - return 0, nil, false - } - - if key < node.key { - return node.getLeftNode().Get(key) } - - rightNode := node.getRightNode() - index, value, exists = rightNode.Get(key) - index += node.size - rightNode.size - return index, value, exists } // GetByIndex retrieves the key-value pair of the node at the given index // in the subtree rooted at the node. -func (node *Node) GetByIndex(index int) (key string, value interface{}) { +func (node *Node) GetByIndex(index int) (key string, value any) { if node.height == 0 { if index == 0 { return node.key, node.value + } else { + panic("GetByIndex asked for invalid index") + } + } else { + // TODO: could improve this by storing the sizes + leftNode := node.getLeftNode() + if index < leftNode.size { + return leftNode.GetByIndex(index) + } else { + return node.getRightNode().GetByIndex(index - leftNode.size) } - panic("GetByIndex asked for invalid index") - } - // TODO: could improve this by storing the sizes - leftNode := node.getLeftNode() - if index < leftNode.size { - return leftNode.GetByIndex(index) } - return node.getRightNode().GetByIndex(index - leftNode.size) } // Set inserts a new node with the given key-value pair into the subtree rooted at the node, // and returns the new root of the subtree and whether an existing node was updated. // // XXX consider a better way to do this... perhaps split Node from Node. -func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) { +func (node *Node) Set(key string, value any) (newSelf *Node, updated bool) { if node == nil { return NewNode(key, value), false } - if node.height == 0 { - return node.setLeaf(key, value) - } - - node = node._copy() - if key < node.key { - node.leftNode, updated = node.getLeftNode().Set(key, value) + if key < node.key { + return &Node{ + key: node.key, + height: 1, + size: 2, + leftNode: NewNode(key, value), + rightNode: node, + }, false + } else if key == node.key { + return NewNode(key, value), true + } else { + return &Node{ + key: key, + height: 1, + size: 2, + leftNode: node, + rightNode: NewNode(key, value), + }, false + } } else { - node.rightNode, updated = node.getRightNode().Set(key, value) - } - - if updated { - return node, updated - } - - node.calcHeightAndSize() - return node.balance(), updated -} - -// setLeaf inserts a new leaf node with the given key-value pair into the subtree rooted at the node, -// and returns the new root of the subtree and whether an existing node was updated. -func (node *Node) setLeaf(key string, value interface{}) (newSelf *Node, updated bool) { - if key == node.key { - return NewNode(key, value), true - } - - if key < node.key { - return &Node{ - key: node.key, - height: 1, - size: 2, - leftNode: NewNode(key, value), - rightNode: node, - }, false + node = node._copy() + if key < node.key { + node.leftNode, updated = node.getLeftNode().Set(key, value) + } else { + node.rightNode, updated = node.getRightNode().Set(key, value) + } + if updated { + return node, updated + } else { + node.calcHeightAndSize() + return node.balance(), updated + } } - - return &Node{ - key: key, - height: 1, - size: 2, - leftNode: node, - rightNode: NewNode(key, value), - }, false } // Remove deletes the node with the given key from the subtree rooted at the node. // returns the new root of the subtree, the new leftmost leaf key (if changed), // the removed value and the removal was successful. func (node *Node) Remove(key string) ( - newNode *Node, newKey string, value interface{}, removed bool, + newNode *Node, newKey string, value any, removed bool, ) { if node == nil { return nil, "", nil, false @@ -187,49 +181,47 @@ func (node *Node) Remove(key string) ( if node.height == 0 { if key == node.key { return nil, "", node.value, true + } else { + return node, "", nil, false } - return node, "", nil, false - } - if key < node.key { - var newLeftNode *Node - newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) - if !removed { - return node, "", value, false - } - if newLeftNode == nil { // left node held value, was removed - return node.rightNode, node.key, value, true + } else { + if key < node.key { + var newLeftNode *Node + newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) + if !removed { + return node, "", value, false + } else if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, true + } + node = node._copy() + node.leftNode = newLeftNode + node.calcHeightAndSize() + node = node.balance() + return node, newKey, value, true + } else { + var newRightNode *Node + newRightNode, newKey, value, removed = node.getRightNode().Remove(key) + if !removed { + return node, "", value, false + } else if newRightNode == nil { // right node held value, was removed + return node.leftNode, "", value, true + } + node = node._copy() + node.rightNode = newRightNode + if newKey != "" { + node.key = newKey + } + node.calcHeightAndSize() + node = node.balance() + return node, "", value, true } - node = node._copy() - node.leftNode = newLeftNode - node.calcHeightAndSize() - node = node.balance() - return node, newKey, value, true - } - - var newRightNode *Node - newRightNode, newKey, value, removed = node.getRightNode().Remove(key) - if !removed { - return node, "", value, false } - if newRightNode == nil { // right node held value, was removed - return node.leftNode, "", value, true - } - node = node._copy() - node.rightNode = newRightNode - if newKey != "" { - node.key = newKey - } - node.calcHeightAndSize() - node = node.balance() - return node, "", value, true } -// getLeftNode returns the left child of the node. func (node *Node) getLeftNode() *Node { return node.leftNode } -// getRightNode returns the right child of the node. func (node *Node) getRightNode() *Node { return node.rightNode } @@ -287,29 +279,30 @@ func (node *Node) calcBalance() int { // TODO: optimize balance & rotate func (node *Node) balance() (newSelf *Node) { balance := node.calcBalance() - if balance >= -1 { - return node - } if balance > 1 { if node.getLeftNode().calcBalance() >= 0 { // Left Left Case return node.rotateRight() + } else { + // Left Right Case + left := node.getLeftNode() + node.leftNode = left.rotateLeft() + return node.rotateRight() } - // Left Right Case - left := node.getLeftNode() - node.leftNode = left.rotateLeft() - return node.rotateRight() } - - if node.getRightNode().calcBalance() <= 0 { - // Right Right Case - return node.rotateLeft() + if balance < -1 { + if node.getRightNode().calcBalance() <= 0 { + // Right Right Case + return node.rotateLeft() + } else { + // Right Left Case + right := node.getRightNode() + node.rightNode = right.rotateRight() + return node.rotateLeft() + } } - - // Right Left Case - right := node.getRightNode() - node.rightNode = right.rotateRight() - return node.rotateLeft() + // Nothing changed + return node } // Shortcut for TraverseInRange. diff --git a/examples/gno.land/p/demo/avl/node_test.gno b/examples/gno.land/p/demo/avl/node_test.gno index 3682cbc7c80..244fde1d324 100644 --- a/examples/gno.land/p/demo/avl/node_test.gno +++ b/examples/gno.land/p/demo/avl/node_test.gno @@ -4,6 +4,8 @@ import ( "sort" "strings" "testing" + + "gno.land/p/demo/ufmt" ) func TestTraverseByOffset(t *testing.T) { @@ -138,7 +140,7 @@ func TestGet(t *testing.T) { input []string getKey string expectIdx int - expectVal interface{} + expectVal any expectExists bool }{ { @@ -205,7 +207,7 @@ func TestGetByIndex(t *testing.T) { input []string idx int expectKey string - expectVal interface{} + expectVal any expectPanic bool }{ { @@ -550,6 +552,175 @@ func TestRotateAndBalance(t *testing.T) { } } +func TestRemoveFromEmptyTree(t *testing.T) { + var tree *Node + newTree, _, val, removed := tree.Remove("NonExistent") + if newTree != nil { + t.Errorf("Removing from an empty tree should still be nil tree.") + } + if val != nil || removed { + t.Errorf("Expected no value and removed=false when removing from empty tree.") + } +} + +func TestBalanceAfterRemoval(t *testing.T) { + tests := []struct { + name string + insertKeys []string + removeKey string + expectedBalance int + }{ + { + name: "balance after removing right node", + insertKeys: []string{"B", "A", "D", "C", "E"}, + removeKey: "E", + expectedBalance: 0, + }, + { + name: "balance after removing left node", + insertKeys: []string{"D", "B", "E", "A", "C"}, + removeKey: "A", + expectedBalance: 0, + }, + { + name: "ensure no lean after removal", + insertKeys: []string{"C", "B", "E", "A", "D", "F"}, + removeKey: "F", + expectedBalance: -1, + }, + { + name: "descending order insert, remove middle node", + insertKeys: []string{"E", "D", "C", "B", "A"}, + removeKey: "C", + expectedBalance: 0, + }, + { + name: "ascending order insert, remove middle node", + insertKeys: []string{"A", "B", "C", "D", "E"}, + removeKey: "C", + expectedBalance: 0, + }, + { + name: "duplicate key insert, remove the duplicated key", + insertKeys: []string{"C", "B", "C", "A", "D"}, + removeKey: "C", + expectedBalance: 1, + }, + { + name: "complex rotation case", + insertKeys: []string{"H", "B", "A", "C", "E", "D", "F", "G"}, + removeKey: "B", + expectedBalance: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.insertKeys { + tree, _ = tree.Set(key, nil) + } + + tree, _, _, _ = tree.Remove(tt.removeKey) + + balance := tree.calcBalance() + if balance != tt.expectedBalance { + t.Errorf("Expected balance factor %d, got %d", tt.expectedBalance, balance) + } + + if balance < -1 || balance > 1 { + t.Errorf("Tree is unbalanced with factor %d", balance) + } + + if errMsg := checkSubtreeBalance(t, tree); errMsg != "" { + t.Errorf("AVL property violation after removal: %s", errMsg) + } + }) + } +} + +func TestBSTProperty(t *testing.T) { + var tree *Node + keys := []string{"D", "B", "F", "A", "C", "E", "G"} + for _, key := range keys { + tree, _ = tree.Set(key, nil) + } + + var result []string + inorderTraversal(t, tree, &result) + + for i := 1; i < len(result); i++ { + if result[i] < result[i-1] { + t.Errorf("BST property violated: %s < %s (index %d)", + result[i], result[i-1], i) + } + } +} + +// inorderTraversal performs an inorder traversal of the tree and returns the keys in a list. +func inorderTraversal(t *testing.T, node *Node, result *[]string) { + t.Helper() + + if node == nil { + return + } + // leaf + if node.height == 0 { + *result = append(*result, node.key) + return + } + inorderTraversal(t, node.leftNode, result) + inorderTraversal(t, node.rightNode, result) +} + +// checkSubtreeBalance checks if all nodes under the given node satisfy the AVL tree conditions. +// The balance factor of all nodes must be ∈ [-1, +1] +func checkSubtreeBalance(t *testing.T, node *Node) string { + t.Helper() + + if node == nil { + return "" + } + + if node.IsLeaf() { + // leaf node must be height=0, size=1 + if node.height != 0 { + return ufmt.Sprintf("Leaf node %s has height %d, expected 0", node.Key(), node.height) + } + if node.size != 1 { + return ufmt.Sprintf("Leaf node %s has size %d, expected 1", node.Key(), node.size) + } + return "" + } + + // check balance factor for current node + balanceFactor := node.calcBalance() + if balanceFactor < -1 || balanceFactor > 1 { + return ufmt.Sprintf("Node %s is unbalanced: balanceFactor=%d", node.Key(), balanceFactor) + } + + // check height / size relationship for children + left, right := node.getLeftNode(), node.getRightNode() + expectedHeight := maxInt8(left.height, right.height) + 1 + if node.height != expectedHeight { + return ufmt.Sprintf("Node %s has incorrect height %d, expected %d", node.Key(), node.height, expectedHeight) + } + expectedSize := left.Size() + right.Size() + if node.size != expectedSize { + return ufmt.Sprintf("Node %s has incorrect size %d, expected %d", node.Key(), node.size, expectedSize) + } + + // recursively check the left/right subtree + if errMsg := checkSubtreeBalance(t, left); errMsg != "" { + return errMsg + } + if errMsg := checkSubtreeBalance(t, right); errMsg != "" { + return errMsg + } + + return "" +} + func slicesEqual(w1, w2 []string) bool { if len(w1) != len(w2) { return false @@ -562,13 +733,6 @@ func slicesEqual(w1, w2 []string) bool { return true } -func maxint8(a, b int8) int8 { - if a > b { - return a - } - return b -} - func reverseSlice(ss []string) { for i := 0; i < len(ss)/2; i++ { j := len(ss) - 1 - i diff --git a/examples/gno.land/p/demo/avl/pager/pager.gno b/examples/gno.land/p/demo/avl/pager/pager.gno index f5f909a473d..78a32cb335d 100644 --- a/examples/gno.land/p/demo/avl/pager/pager.gno +++ b/examples/gno.land/p/demo/avl/pager/pager.gno @@ -5,13 +5,13 @@ import ( "net/url" "strconv" - "gno.land/p/demo/avl" + "gno.land/p/demo/avl/rotree" "gno.land/p/demo/ufmt" ) // Pager is a struct that holds the AVL tree and pagination parameters. type Pager struct { - Tree avl.ITree + Tree rotree.IReadOnlyTree PageQueryParam string SizeQueryParam string DefaultPageSize int @@ -33,11 +33,11 @@ type Page struct { // Item represents a key-value pair in the AVL tree. type Item struct { Key string - Value interface{} + Value any } // NewPager creates a new Pager with default values. -func NewPager(tree avl.ITree, defaultPageSize int, reversed bool) *Pager { +func NewPager(tree rotree.IReadOnlyTree, defaultPageSize int, reversed bool) *Pager { return &Pager{ Tree: tree, PageQueryParam: "page", @@ -90,12 +90,12 @@ func (p *Pager) GetPageWithSize(pageNumber, pageSize int) *Page { items := []Item{} if p.Reversed { - p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool { + p.Tree.ReverseIterateByOffset(startIndex, endIndex-startIndex, func(key string, value any) bool { items = append(items, Item{Key: key, Value: value}) return false }) } else { - p.Tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, value interface{}) bool { + p.Tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, value any) bool { items = append(items, Item{Key: key, Value: value}) return false }) diff --git a/examples/gno.land/p/demo/avl/rolist/rolist.gno b/examples/gno.land/p/demo/avl/rolist/rolist.gno index 23a85d9c885..c75acc79087 100644 --- a/examples/gno.land/p/demo/avl/rolist/rolist.gno +++ b/examples/gno.land/p/demo/avl/rolist/rolist.gno @@ -22,7 +22,7 @@ // // // Create a safe transformation function that copies the struct // // while excluding sensitive data -// makeEntrySafeFn := func(v interface{}) interface{} { +// makeEntrySafeFn := func(v any) any { // u := v.(*User) // return &User{ // Name: u.Name, @@ -49,15 +49,15 @@ import ( // IReadOnlyList defines the read-only operations available on a list. type IReadOnlyList interface { Len() int - Get(index int) interface{} - Slice(startIndex, endIndex int) []interface{} - ForEach(fn func(index int, value interface{}) bool) + Get(index int) any + Slice(startIndex, endIndex int) []any + ForEach(fn func(index int, value any) bool) } // ReadOnlyList wraps a list.List and provides read-only access. type ReadOnlyList struct { list *list.List - makeEntrySafeFn func(interface{}) interface{} + makeEntrySafeFn func(any) any } // Verify interface implementations @@ -66,7 +66,7 @@ var _ IReadOnlyList = (interface{ list.IList })(nil) // is subset of list.IList // Wrap creates a new ReadOnlyList from an existing list.List and a safety transformation function. // If makeEntrySafeFn is nil, values will be returned as-is without transformation. -func Wrap(list *list.List, makeEntrySafeFn func(interface{}) interface{}) *ReadOnlyList { +func Wrap(list *list.List, makeEntrySafeFn func(any) any) *ReadOnlyList { return &ReadOnlyList{ list: list, makeEntrySafeFn: makeEntrySafeFn, @@ -74,7 +74,7 @@ func Wrap(list *list.List, makeEntrySafeFn func(interface{}) interface{}) *ReadO } // getSafeValue applies the makeEntrySafeFn if it exists, otherwise returns the original value -func (rol *ReadOnlyList) getSafeValue(value interface{}) interface{} { +func (rol *ReadOnlyList) getSafeValue(value any) any { if rol.makeEntrySafeFn == nil { return value } @@ -88,7 +88,7 @@ func (rol *ReadOnlyList) Len() int { // Get returns the value at the specified index, converted to a safe format. // Returns nil if index is out of bounds. -func (rol *ReadOnlyList) Get(index int) interface{} { +func (rol *ReadOnlyList) Get(index int) any { value := rol.list.Get(index) if value == nil { return nil @@ -98,13 +98,13 @@ func (rol *ReadOnlyList) Get(index int) interface{} { // Slice returns a slice of values from startIndex (inclusive) to endIndex (exclusive), // with all values converted to a safe format. -func (rol *ReadOnlyList) Slice(startIndex, endIndex int) []interface{} { +func (rol *ReadOnlyList) Slice(startIndex, endIndex int) []any { values := rol.list.Slice(startIndex, endIndex) if values == nil { return nil } - result := make([]interface{}, len(values)) + result := make([]any, len(values)) for i, v := range values { result[i] = rol.getSafeValue(v) } @@ -112,8 +112,8 @@ func (rol *ReadOnlyList) Slice(startIndex, endIndex int) []interface{} { } // ForEach iterates through all elements in the list, providing safe versions of the values. -func (rol *ReadOnlyList) ForEach(fn func(index int, value interface{}) bool) { - rol.list.ForEach(func(index int, value interface{}) bool { +func (rol *ReadOnlyList) ForEach(fn func(index int, value any) bool) { + rol.list.ForEach(func(index int, value any) bool { return fn(index, rol.getSafeValue(value)) }) } diff --git a/examples/gno.land/p/demo/avl/rolist/rolist_test.gno b/examples/gno.land/p/demo/avl/rolist/rolist_test.gno index 03b0a8cba30..40790a03f5b 100644 --- a/examples/gno.land/p/demo/avl/rolist/rolist_test.gno +++ b/examples/gno.land/p/demo/avl/rolist/rolist_test.gno @@ -35,7 +35,7 @@ func TestExample(t *testing.T) { // Define a makeEntrySafeFn that: // 1. Creates a defensive copy of the User struct // 2. Omits sensitive internal data - makeEntrySafeFn := func(v interface{}) interface{} { + makeEntrySafeFn := func(v any) any { originalUser := v.(*User) return &User{ ID: originalUser.ID, @@ -93,7 +93,7 @@ func TestExample(t *testing.T) { // Test ForEach functionality t.Run("ForEach Users", func(t *testing.T) { count := 0 - roList.ForEach(func(index int, value interface{}) bool { + roList.ForEach(func(index int, value any) bool { user := value.(*User) if user.Internal != "" { t.Error("Sensitive data exposed during iteration") @@ -132,7 +132,7 @@ func TestNilMakeEntrySafeFn(t *testing.T) { func TestReadOnlyList(t *testing.T) { // Example of a makeEntrySafeFn that appends "_readonly" to demonstrate transformation - makeEntrySafeFn := func(value interface{}) interface{} { + makeEntrySafeFn := func(value any) any { return value.(string) + "_readonly" } @@ -144,7 +144,7 @@ func TestReadOnlyList(t *testing.T) { tests := []struct { name string index int - expected interface{} + expected any }{ {"ExistingIndex0", 0, "value1_readonly"}, {"ExistingIndex1", 1, "value2_readonly"}, diff --git a/examples/gno.land/p/demo/avl/rotree/rotree.gno b/examples/gno.land/p/demo/avl/rotree/rotree.gno index 17cb4e20ced..10867064dbb 100644 --- a/examples/gno.land/p/demo/avl/rotree/rotree.gno +++ b/examples/gno.land/p/demo/avl/rotree/rotree.gno @@ -22,7 +22,7 @@ // // // Create a safe transformation function that copies the struct // // while excluding sensitive data -// makeEntrySafeFn := func(v interface{}) interface{} { +// makeEntrySafeFn := func(v any) any { // u := v.(*User) // return &User{ // Name: u.Name, @@ -57,19 +57,19 @@ import ( // // 2. Defensive copying: For mutable types like slices or maps, you should create a deep copy // to prevent modification of the original data. -// Example: func(v interface{}) interface{} { return append([]int{}, v.([]int)...) } +// Example: func(v any) any { return append([]int{}, v.([]int)...) } // // 3. Read-only wrapper: Return a read-only version of the object that implements // a limited interface. -// Example: func(v interface{}) interface{} { return NewReadOnlyObject(v) } +// Example: func(v any) any { return NewReadOnlyObject(v) } // // 4. DAO transformation: Transform the object into a data access object that // controls how the underlying data can be accessed. -// Example: func(v interface{}) interface{} { return NewDAO(v) } +// Example: func(v any) any { return NewDAO(v) } // // The function ensures that the returned object is safe to expose to untrusted code, // preventing unauthorized modifications to the original data structure. -func Wrap(tree *avl.Tree, makeEntrySafeFn func(interface{}) interface{}) *ReadOnlyTree { +func Wrap(tree *avl.Tree, makeEntrySafeFn func(any) any) *ReadOnlyTree { return &ReadOnlyTree{ tree: tree, makeEntrySafeFn: makeEntrySafeFn, @@ -79,15 +79,15 @@ func Wrap(tree *avl.Tree, makeEntrySafeFn func(interface{}) interface{}) *ReadOn // ReadOnlyTree wraps an avl.Tree and provides read-only access. type ReadOnlyTree struct { tree *avl.Tree - makeEntrySafeFn func(interface{}) interface{} + makeEntrySafeFn func(any) any } // IReadOnlyTree defines the read-only operations available on a tree. type IReadOnlyTree interface { Size() int Has(key string) bool - Get(key string) (interface{}, bool) - GetByIndex(index int) (string, interface{}) + Get(key string) (any, bool) + GetByIndex(index int) (string, any) Iterate(start, end string, cb avl.IterCbFn) bool ReverseIterate(start, end string, cb avl.IterCbFn) bool IterateByOffset(offset int, count int, cb avl.IterCbFn) bool @@ -101,7 +101,7 @@ var ( ) // getSafeValue applies the makeEntrySafeFn if it exists, otherwise returns the original value -func (roTree *ReadOnlyTree) getSafeValue(value interface{}) interface{} { +func (roTree *ReadOnlyTree) getSafeValue(value any) any { if roTree.makeEntrySafeFn == nil { return value } @@ -119,7 +119,7 @@ func (roTree *ReadOnlyTree) Has(key string) bool { } // Get retrieves the value associated with the given key, converted to a safe format. -func (roTree *ReadOnlyTree) Get(key string) (interface{}, bool) { +func (roTree *ReadOnlyTree) Get(key string) (any, bool) { value, exists := roTree.tree.Get(key) if !exists { return nil, false @@ -128,50 +128,50 @@ func (roTree *ReadOnlyTree) Get(key string) (interface{}, bool) { } // GetByIndex retrieves the key-value pair at the specified index in the tree, with the value converted to a safe format. -func (roTree *ReadOnlyTree) GetByIndex(index int) (string, interface{}) { +func (roTree *ReadOnlyTree) GetByIndex(index int) (string, any) { key, value := roTree.tree.GetByIndex(index) return key, roTree.getSafeValue(value) } // Iterate performs an in-order traversal of the tree within the specified key range. func (roTree *ReadOnlyTree) Iterate(start, end string, cb avl.IterCbFn) bool { - return roTree.tree.Iterate(start, end, func(key string, value interface{}) bool { + return roTree.tree.Iterate(start, end, func(key string, value any) bool { return cb(key, roTree.getSafeValue(value)) }) } // ReverseIterate performs a reverse in-order traversal of the tree within the specified key range. func (roTree *ReadOnlyTree) ReverseIterate(start, end string, cb avl.IterCbFn) bool { - return roTree.tree.ReverseIterate(start, end, func(key string, value interface{}) bool { + return roTree.tree.ReverseIterate(start, end, func(key string, value any) bool { return cb(key, roTree.getSafeValue(value)) }) } // IterateByOffset performs an in-order traversal of the tree starting from the specified offset. func (roTree *ReadOnlyTree) IterateByOffset(offset int, count int, cb avl.IterCbFn) bool { - return roTree.tree.IterateByOffset(offset, count, func(key string, value interface{}) bool { + return roTree.tree.IterateByOffset(offset, count, func(key string, value any) bool { return cb(key, roTree.getSafeValue(value)) }) } // ReverseIterateByOffset performs a reverse in-order traversal of the tree starting from the specified offset. func (roTree *ReadOnlyTree) ReverseIterateByOffset(offset int, count int, cb avl.IterCbFn) bool { - return roTree.tree.ReverseIterateByOffset(offset, count, func(key string, value interface{}) bool { + return roTree.tree.ReverseIterateByOffset(offset, count, func(key string, value any) bool { return cb(key, roTree.getSafeValue(value)) }) } // Set is not supported on ReadOnlyTree and will panic. -func (roTree *ReadOnlyTree) Set(key string, value interface{}) bool { +func (roTree *ReadOnlyTree) Set(key string, value any) bool { panic("Set operation not supported on ReadOnlyTree") } // Remove is not supported on ReadOnlyTree and will panic. -func (roTree *ReadOnlyTree) Remove(key string) (value interface{}, removed bool) { +func (roTree *ReadOnlyTree) Remove(key string) (value any, removed bool) { panic("Remove operation not supported on ReadOnlyTree") } // RemoveByIndex is not supported on ReadOnlyTree and will panic. -func (roTree *ReadOnlyTree) RemoveByIndex(index int) (key string, value interface{}) { +func (roTree *ReadOnlyTree) RemoveByIndex(index int) (key string, value any) { panic("RemoveByIndex operation not supported on ReadOnlyTree") } diff --git a/examples/gno.land/p/demo/avl/rotree/rotree_test.gno b/examples/gno.land/p/demo/avl/rotree/rotree_test.gno index fbc14bd688d..6c43b4488fb 100644 --- a/examples/gno.land/p/demo/avl/rotree/rotree_test.gno +++ b/examples/gno.land/p/demo/avl/rotree/rotree_test.gno @@ -33,7 +33,7 @@ func TestExample(t *testing.T) { // Define a makeEntrySafeFn that: // 1. Creates a defensive copy of the User struct // 2. Omits sensitive internal data - makeEntrySafeFn := func(v interface{}) interface{} { + makeEntrySafeFn := func(v any) any { originalUser := v.(*User) return &User{ ID: originalUser.ID, @@ -77,7 +77,7 @@ func TestExample(t *testing.T) { // Test iterating over users t.Run("Iterate Users", func(t *testing.T) { count := 0 - roTree.Iterate("", "", func(key string, value interface{}) bool { + roTree.Iterate("", "", func(key string, value any) bool { user := value.(*User) // Verify each user has empty Internal field if user.Internal != "" { @@ -112,7 +112,7 @@ func TestExample(t *testing.T) { func TestReadOnlyTree(t *testing.T) { // Example of a makeEntrySafeFn that appends "_readonly" to demonstrate transformation - makeEntrySafeFn := func(value interface{}) interface{} { + makeEntrySafeFn := func(value any) any { return value.(string) + "_readonly" } @@ -126,7 +126,7 @@ func TestReadOnlyTree(t *testing.T) { tests := []struct { name string key string - expected interface{} + expected any exists bool }{ {"ExistingKey1", "key1", "value1_readonly", true}, @@ -152,18 +152,18 @@ func TestMakeEntrySafeFnVariants(t *testing.T) { tests := []struct { name string - makeEntrySafeFn func(interface{}) interface{} + makeEntrySafeFn func(any) any key string - validate func(t *testing.T, value interface{}) + validate func(t *testing.T, value any) }{ { name: "Defensive Copy Slice", - makeEntrySafeFn: func(v interface{}) interface{} { + makeEntrySafeFn: func(v any) any { original := v.([]int) return append([]int{}, original...) }, key: "slice", - validate: func(t *testing.T, value interface{}) { + validate: func(t *testing.T, value any) { slice := value.([]int) // Modify the returned slice slice[0] = 999 @@ -212,7 +212,7 @@ func TestNilMakeEntrySafeFn(t *testing.T) { } // Test through iteration as well - roTree.Iterate("", "", func(key string, value interface{}) bool { + roTree.Iterate("", "", func(key string, value any) bool { retrievedSlice := value.([]int) if &retrievedSlice[0] != &originalValue[0] { t.Error("Expected to get back the original slice reference in iteration") diff --git a/examples/gno.land/p/demo/avl/tree.gno b/examples/gno.land/p/demo/avl/tree.gno index 3834246d2cd..c625fd7b635 100644 --- a/examples/gno.land/p/demo/avl/tree.gno +++ b/examples/gno.land/p/demo/avl/tree.gno @@ -5,8 +5,8 @@ type ITree interface { Size() int Has(key string) bool - Get(key string) (value interface{}, exists bool) - GetByIndex(index int) (key string, value interface{}) + Get(key string) (value any, exists bool) + GetByIndex(index int) (key string, value any) Iterate(start, end string, cb IterCbFn) bool ReverseIterate(start, end string, cb IterCbFn) bool IterateByOffset(offset int, count int, cb IterCbFn) bool @@ -14,11 +14,11 @@ type ITree interface { // write operations - Set(key string, value interface{}) (updated bool) - Remove(key string) (value interface{}, removed bool) + Set(key string, value any) (updated bool) + Remove(key string) (value any, removed bool) } -type IterCbFn func(key string, value interface{}) bool +type IterCbFn func(key string, value any) bool //---------------------------------------- // Tree @@ -48,21 +48,21 @@ func (tree *Tree) Has(key string) (has bool) { // Get retrieves the value associated with the given key. // It returns the value and a boolean indicating whether the key exists. -func (tree *Tree) Get(key string) (value interface{}, exists bool) { +func (tree *Tree) Get(key string) (value any, exists bool) { _, value, exists = tree.node.Get(key) return } // GetByIndex retrieves the key-value pair at the specified index in the tree. // It returns the key and value at the given index. -func (tree *Tree) GetByIndex(index int) (key string, value interface{}) { +func (tree *Tree) GetByIndex(index int) (key string, value any) { return tree.node.GetByIndex(index) } // Set inserts a key-value pair into the tree. // If the key already exists, the value will be updated. // It returns a boolean indicating whether the key was newly inserted or updated. -func (tree *Tree) Set(key string, value interface{}) (updated bool) { +func (tree *Tree) Set(key string, value any) (updated bool) { newnode, updated := tree.node.Set(key, value) tree.node = newnode return updated @@ -70,7 +70,7 @@ func (tree *Tree) Set(key string, value interface{}) (updated bool) { // Remove removes a key-value pair from the tree. // It returns the removed value and a boolean indicating whether the key was found and removed. -func (tree *Tree) Remove(key string) (value interface{}, removed bool) { +func (tree *Tree) Remove(key string) (value any, removed bool) { newnode, _, value, removed := tree.node.Remove(key) tree.node = newnode return value, removed diff --git a/examples/gno.land/p/demo/avl/tree_test.gno b/examples/gno.land/p/demo/avl/tree_test.gno index 8f6efcc5bad..651c0c437e0 100644 --- a/examples/gno.land/p/demo/avl/tree_test.gno +++ b/examples/gno.land/p/demo/avl/tree_test.gno @@ -95,7 +95,7 @@ func TestTreeIterate(t *testing.T) { tree.Set("key3", "value3") var keys []string - tree.Iterate("", "", func(key string, value interface{}) bool { + tree.Iterate("", "", func(key string, value any) bool { keys = append(keys, key) return false }) @@ -113,7 +113,7 @@ func TestTreeReverseIterate(t *testing.T) { tree.Set("key3", "value3") var keys []string - tree.ReverseIterate("", "", func(key string, value interface{}) bool { + tree.ReverseIterate("", "", func(key string, value any) bool { keys = append(keys, key) return false }) @@ -131,7 +131,7 @@ func TestTreeIterateByOffset(t *testing.T) { tree.Set("key3", "value3") var keys []string - tree.IterateByOffset(1, 2, func(key string, value interface{}) bool { + tree.IterateByOffset(1, 2, func(key string, value any) bool { keys = append(keys, key) return false }) @@ -149,7 +149,7 @@ func TestTreeReverseIterateByOffset(t *testing.T) { tree.Set("key3", "value3") var keys []string - tree.ReverseIterateByOffset(1, 2, func(key string, value interface{}) bool { + tree.ReverseIterateByOffset(1, 2, func(key string, value any) bool { keys = append(keys, key) return false }) diff --git a/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno index e5fe33cacad..ba005f3cc77 100644 --- a/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno +++ b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno @@ -30,7 +30,7 @@ func IterateByteStringKeysByPrefix(tree avl.ITree, prefix string, cb avl.IterCbF // The prefix and keys are treated as byte strings, ignoring possible multi-byte Unicode runes. func ListByteStringKeysByPrefix(tree avl.ITree, prefix string, maxResults int) []string { result := []string{} - IterateByteStringKeysByPrefix(tree, prefix, func(key string, value interface{}) bool { + IterateByteStringKeysByPrefix(tree, prefix, func(key string, value any) bool { result = append(result, key) if len(result) >= maxResults { return true diff --git a/examples/gno.land/p/demo/blog/blog.gno b/examples/gno.land/p/demo/blog/blog.gno index aad2173b4b8..fce12d48d9c 100644 --- a/examples/gno.land/p/demo/blog/blog.gno +++ b/examples/gno.land/p/demo/blog/blog.gno @@ -27,7 +27,7 @@ func (b Blog) RenderLastPostsWidget(limit int) string { output := "" i := 0 - b.PostsPublished.ReverseIterate("", "", func(key string, value interface{}) bool { + b.PostsPublished.ReverseIterate("", "", func(key string, value any) bool { p := value.(*Post) output += ufmt.Sprintf("- [%s](%s)\n", p.Title, p.URL()) i++ @@ -47,7 +47,7 @@ func (b Blog) RenderHome(res *mux.ResponseWriter, req *mux.Request) { } res.Write("
") - b.PostsPublished.ReverseIterate("", "", func(key string, value interface{}) bool { + b.PostsPublished.ReverseIterate("", "", func(key string, value any) bool { post := value.(*Post) res.Write(post.RenderListItem()) return false @@ -81,7 +81,7 @@ func (b Blog) RenderPost(res *mux.ResponseWriter, req *mux.Request) { res.Write("
Comment section\n\n") // comments - p.Comments.ReverseIterate("", "", func(key string, value interface{}) bool { + p.Comments.ReverseIterate("", "", func(key string, value any) bool { comment := value.(*Comment) res.Write(comment.RenderListItem()) return false @@ -109,7 +109,7 @@ func (b Blog) RenderTag(res *mux.ResponseWriter, req *mux.Request) { } nb := 0 - b.Posts.Iterate("", "", func(key string, value interface{}) bool { + b.Posts.Iterate("", "", func(key string, value any) bool { post := value.(*Post) if !post.HasTag(slug) { return false diff --git a/examples/gno.land/p/demo/btree/btree_test.gno b/examples/gno.land/p/demo/btree/btree_test.gno index a0f7c1c55ca..67a08ff19f3 100644 --- a/examples/gno.land/p/demo/btree/btree_test.gno +++ b/examples/gno.land/p/demo/btree/btree_test.gno @@ -9,8 +9,8 @@ import ( // Content represents a key-value pair where the Key can be either an int or string // and the Value can be any type. type Content struct { - Key interface{} - Value interface{} + Key any + Value any } // Less compares two Content records by their Keys. @@ -120,7 +120,6 @@ func TestLen(t *testing.T) { if length != 123 { t.Errorf("Length is incorrect. Expected 123, but got %d.", length) } - } func TestHas(t *testing.T) { @@ -138,7 +137,7 @@ func TestHas(t *testing.T) { } func TestMin(t *testing.T) { - min := Content(genericSeeding(New(WithDegree(10)), 53).Min()) + min := genericSeeding(New(WithDegree(10)), 53).Min().(Content) if min.Key != 0 { t.Errorf("Minimum should have been 0, but it was reported as %d.", min) @@ -146,24 +145,24 @@ func TestMin(t *testing.T) { } func TestMax(t *testing.T) { - max := Content(genericSeeding(New(WithDegree(10)), 53).Min()) + max := genericSeeding(New(WithDegree(10)), 53).Max().(Content) - if max.Key != 0 { - t.Errorf("Minimum should have been 0, but it was reported as %d.", max) + if max.Key != 52 { + t.Errorf("Maximum should have been 52, but it was reported as %d.", max) } } func TestGet(t *testing.T) { tree := genericSeeding(New(WithDegree(10)), 40) - if Content(tree.Get(Content{Key: 7})).Value != "Value_7" { - t.Errorf("Get(7) should have returned 'Value_7', but it returned %v.", tree.Get(Content{Key: 7})) + if val := tree.Get(Content{Key: 7}); val != nil && val.(Content).Value != "Value_7" { + t.Errorf("Get(7) should have returned 'Value_7', but it returned %v.", val) } - if Content(tree.Get(Content{Key: 39})).Value != "Value_39" { - t.Errorf("Get(40) should have returnd 'Value_39', but it returned %v.", tree.Get(Content{Key: 39})) + if val := tree.Get(Content{Key: 39}); val != nil && val.(Content).Value != "Value_39" { + t.Errorf("Get(39) should have returned 'Value_39', but it returned %v.", val) } - if tree.Get(Content{Key: 1111}) != nil { - t.Errorf("Get(1111) returned %v, but it should be nil.", Content(tree.Get(Content{Key: 1111}))) + if val := tree.Get(Content{Key: 1111}); val != nil { + t.Errorf("Get(1111) returned %v, but it should be nil.", val) } } @@ -174,8 +173,8 @@ func TestDescend(t *testing.T) { found := []int{} tree.Descend(func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -191,8 +190,8 @@ func TestDescendGreaterThan(t *testing.T) { found := []int{} tree.DescendGreaterThan(Content{Key: 4}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -208,8 +207,8 @@ func TestDescendLessOrEqual(t *testing.T) { found := []int{} tree.DescendLessOrEqual(Content{Key: 4}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -225,8 +224,8 @@ func TestDescendRange(t *testing.T) { found := []int{} tree.DescendRange(Content{Key: 6}, Content{Key: 1}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -242,8 +241,8 @@ func TestAscend(t *testing.T) { found := []int{} tree.Ascend(func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -259,8 +258,8 @@ func TestAscendGreaterOrEqual(t *testing.T) { found := []int{} tree.AscendGreaterOrEqual(Content{Key: 5}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) @@ -276,13 +275,13 @@ func TestAscendLessThan(t *testing.T) { found := []int{} tree.AscendLessThan(Content{Key: 5}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) if intSlicesCompare(expected, found) != 0 { - t.Errorf("DescendLessOrEqual returned the wrong sequence. Expected %v, but got %v.", expected, found) + t.Errorf("AscendLessThan returned the wrong sequence. Expected %v, but got %v.", expected, found) } } @@ -293,13 +292,13 @@ func TestAscendRange(t *testing.T) { found := []int{} tree.AscendRange(Content{Key: 2}, Content{Key: 7}, func(_record Record) bool { - record := Content(_record) - found = append(found, int(record.Key)) + record := _record.(Content) + found = append(found, record.Key.(int)) return true }) if intSlicesCompare(expected, found) != 0 { - t.Errorf("DescendRange returned the wrong sequence. Expected %v, but got %v.", expected, found) + t.Errorf("AscendRange returned the wrong sequence. Expected %v, but got %v.", expected, found) } } @@ -309,11 +308,11 @@ func TestDeleteMin(t *testing.T) { expected := []int{0, 1, 2, 3, 4} found := []int{} - found = append(found, int(Content(tree.DeleteMin()).Key)) - found = append(found, int(Content(tree.DeleteMin()).Key)) - found = append(found, int(Content(tree.DeleteMin()).Key)) - found = append(found, int(Content(tree.DeleteMin()).Key)) - found = append(found, int(Content(tree.DeleteMin()).Key)) + found = append(found, tree.DeleteMin().(Content).Key.(int)) + found = append(found, tree.DeleteMin().(Content).Key.(int)) + found = append(found, tree.DeleteMin().(Content).Key.(int)) + found = append(found, tree.DeleteMin().(Content).Key.(int)) + found = append(found, tree.DeleteMin().(Content).Key.(int)) if intSlicesCompare(expected, found) != 0 { t.Errorf("5 rounds of DeleteMin returned the wrong elements. Expected %v, but got %v.", expected, found) @@ -326,11 +325,11 @@ func TestShift(t *testing.T) { expected := []int{0, 1, 2, 3, 4} found := []int{} - found = append(found, int(Content(tree.Shift()).Key)) - found = append(found, int(Content(tree.Shift()).Key)) - found = append(found, int(Content(tree.Shift()).Key)) - found = append(found, int(Content(tree.Shift()).Key)) - found = append(found, int(Content(tree.Shift()).Key)) + found = append(found, tree.Shift().(Content).Key.(int)) + found = append(found, tree.Shift().(Content).Key.(int)) + found = append(found, tree.Shift().(Content).Key.(int)) + found = append(found, tree.Shift().(Content).Key.(int)) + found = append(found, tree.Shift().(Content).Key.(int)) if intSlicesCompare(expected, found) != 0 { t.Errorf("5 rounds of Shift returned the wrong elements. Expected %v, but got %v.", expected, found) @@ -343,14 +342,14 @@ func TestDeleteMax(t *testing.T) { expected := []int{99, 98, 97, 96, 95} found := []int{} - found = append(found, int(Content(tree.DeleteMax()).Key)) - found = append(found, int(Content(tree.DeleteMax()).Key)) - found = append(found, int(Content(tree.DeleteMax()).Key)) - found = append(found, int(Content(tree.DeleteMax()).Key)) - found = append(found, int(Content(tree.DeleteMax()).Key)) + found = append(found, tree.DeleteMax().(Content).Key.(int)) + found = append(found, tree.DeleteMax().(Content).Key.(int)) + found = append(found, tree.DeleteMax().(Content).Key.(int)) + found = append(found, tree.DeleteMax().(Content).Key.(int)) + found = append(found, tree.DeleteMax().(Content).Key.(int)) if intSlicesCompare(expected, found) != 0 { - t.Errorf("5 rounds of DeleteMin returned the wrong elements. Expected %v, but got %v.", expected, found) + t.Errorf("5 rounds of DeleteMax returned the wrong elements. Expected %v, but got %v.", expected, found) } } @@ -360,14 +359,14 @@ func TestPop(t *testing.T) { expected := []int{99, 98, 97, 96, 95} found := []int{} - found = append(found, int(Content(tree.Pop()).Key)) - found = append(found, int(Content(tree.Pop()).Key)) - found = append(found, int(Content(tree.Pop()).Key)) - found = append(found, int(Content(tree.Pop()).Key)) - found = append(found, int(Content(tree.Pop()).Key)) + found = append(found, tree.Pop().(Content).Key.(int)) + found = append(found, tree.Pop().(Content).Key.(int)) + found = append(found, tree.Pop().(Content).Key.(int)) + found = append(found, tree.Pop().(Content).Key.(int)) + found = append(found, tree.Pop().(Content).Key.(int)) if intSlicesCompare(expected, found) != 0 { - t.Errorf("5 rounds of DeleteMin returned the wrong elements. Expected %v, but got %v.", expected, found) + t.Errorf("5 rounds of Pop returned the wrong elements. Expected %v, but got %v.", expected, found) } } @@ -383,13 +382,15 @@ func TestInsertGet(t *testing.T) { } for count := 0; count < 20; count++ { - if tree.Get(Content{Key: count}) != expected[count] { - t.Errorf("Insert/Get doesn't appear to be working. Expected to retrieve %v with key %d, but got %v.", expected[count], count, tree.Get(Content{Key: count})) + val := tree.Get(Content{Key: count}) + if val == nil || val.(Content) != expected[count] { + t.Errorf("Insert/Get doesn't appear to be working. Expected to retrieve %v with key %d, but got %v.", expected[count], count, val) } } } func TestClone(t *testing.T) { + // Implement the clone test } // ***** The following tests are functional or stress testing type tests. @@ -398,7 +399,7 @@ func TestBTree(t *testing.T) { // Create a B-Tree of degree 3 tree := New(WithDegree(3)) - //insertData := []Content{} + // insertData := []Content{} var insertData ContentSlice // Insert integer keys @@ -440,18 +441,18 @@ func TestBTree(t *testing.T) { val := tree.Get(test.test) if test.expected { - if val != nil && Content(val).Value == test.test.Value { + if val != nil && val.(Content).Value == test.test.Value { t.Logf("Found expected key:value %v:%v", test.test.Key, test.test.Value) } else { if val == nil { t.Logf("Didn't find %v, but expected", test.test.Key) } else { - t.Errorf("Expected key %v:%v, but found %v:%v.", test.test.Key, test.test.Value, Content(val).Key, Content(val).Value) + t.Errorf("Expected key %v:%v, but found %v:%v.", test.test.Key, test.test.Value, val.(Content).Key, val.(Content).Value) } } } else { if val != nil { - t.Errorf("Did not expect key %v, but found key:value %v:%v", test.test.Key, Content(val).Key, Content(val).Value) + t.Errorf("Did not expect key %v, but found key:value %v:%v", test.test.Key, val.(Content).Key, val.(Content).Value) } else { t.Logf("Didn't find %v, but wasn't expected", test.test.Key) } @@ -474,7 +475,7 @@ func TestBTree(t *testing.T) { t.Logf("Tree Length: %d", tree.Len()) tree.Ascend(func(_record Record) bool { - record := Content(_record) + record := _record.(Content) t.Logf("Key:Value == %v:%v", record.Key, record.Value) if record.Key != sortedInsertData[pos].Key { t.Errorf("Out of order! Expected %v, but got %v", sortedInsertData[pos].Key, record.Key) @@ -487,7 +488,7 @@ func TestBTree(t *testing.T) { pos = len(sortedInsertData) - 1 tree.Descend(func(_record Record) bool { - record := Content(_record) + record := _record.(Content) t.Logf("Key:Value == %v:%v", record.Key, record.Value) if record.Key != sortedInsertData[pos].Key { t.Errorf("Out of order! Expected %v, but got %v", sortedInsertData[pos].Key, record.Key) @@ -497,10 +498,10 @@ func TestBTree(t *testing.T) { }) deleteTests := []Content{ - Content{Key: 10, Value: "Value_10"}, - Content{Key: 15, Value: ""}, - Content{Key: "banana", Value: "Fruit_banana"}, - Content{Key: "kiwi", Value: ""}, + {Key: 10, Value: "Value_10"}, + {Key: 15, Value: ""}, + {Key: "banana", Value: "Fruit_banana"}, + {Key: "kiwi", Value: ""}, } for _, test := range deleteTests { fmt.Printf("\nDeleting %+v\n", test) @@ -514,98 +515,21 @@ func TestBTree(t *testing.T) { for _, test := range deleteTests { val := tree.Get(test) if val != nil { - t.Errorf("Did not expect key %v, but found key:value %v:%v", test.Key, Content(val).Key, Content(val).Value) + t.Errorf("Did not expect key %v, but found key:value %v:%v", test.Key, val.(Content).Key, val.(Content).Value) } else { t.Logf("Didn't find %v, but wasn't expected", test.Key) } } } -func TestStress(t *testing.T) { - // Loop through creating B-Trees with a range of degrees from 3 to 12, stepping by 3. - // Insert 1000 records into each tree, then search for each record. - // Delete half of the records, skipping every other one, then search for each record. - - for degree := 3; degree <= 12; degree += 3 { - t.Logf("Testing B-Tree of degree %d\n", degree) - tree := New(WithDegree(degree)) - - // Insert 1000 records - t.Logf("Inserting 1000 records\n") - for i := 0; i < 1000; i++ { - content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} - tree.Insert(content) - } - - // Search for all records - for i := 0; i < 1000; i++ { - content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} - val := tree.Get(content) - if val == nil { - t.Errorf("Expected key %v, but didn't find it", content.Key) - } - } - - // Delete half of the records - for i := 0; i < 1000; i += 2 { - content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} - tree.Delete(content) - } - - // Search for all records - for i := 0; i < 1000; i++ { - content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} - val := tree.Get(content) - if i%2 == 0 { - if val != nil { - t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, Content(val).Key, Content(val).Value) - } - } else { - if val == nil { - t.Errorf("Expected key %v, but didn't find it", content.Key) - } - } - } - } - - // Now create a very large tree, with 100000 records - // Then delete roughly one third of them, using a very basic random number generation scheme - // (implement it right here) to determine which records to delete. - // Print a few lines using Logf to let the user know what's happening. - - t.Logf("Testing B-Tree of degree 10 with 100000 records\n") - tree := New(WithDegree(10)) - - // Insert 100000 records - t.Logf("Inserting 100000 records\n") - for i := 0; i < 100000; i++ { - content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} - tree.Insert(content) - } - - // Implement a very basic random number generator - seed := 0 - random := func() int { - seed = (seed*1103515245 + 12345) & 0x7fffffff - return seed - } - - // Delete one third of the records - t.Logf("Deleting one third of the records\n") - for i := 0; i < 35000; i++ { - content := Content{Key: random() % 100000, Value: fmt.Sprintf("Value_%d", i)} - tree.Delete(content) - } -} - -// Write a test that populates a large B-Tree with 10000 records. +// Write a test that populates a large B-Tree with 1000 records. // It should then `Clone` the tree, make some changes to both the original and the clone, // And then clone the clone, and make some changes to all three trees, and then check that the changes are isolated // to the tree they were made in. - func TestBTreeCloneIsolation(t *testing.T) { - t.Logf("Creating B-Tree of degree 10 with 10000 records\n") - tree := genericSeeding(New(WithDegree(10)), 10000) + t.Logf("Creating B-Tree of degree 10 with 1000 records\n") + size := 1000 + tree := genericSeeding(New(WithDegree(10)), size) // Clone the tree t.Logf("Cloning the tree\n") @@ -613,7 +537,7 @@ func TestBTreeCloneIsolation(t *testing.T) { // Make some changes to the original and the clone t.Logf("Making changes to the original and the clone\n") - for i := 0; i < 10000; i += 2 { + for i := 0; i < size; i += 2 { content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} tree.Delete(content) content = Content{Key: i + 1, Value: fmt.Sprintf("Value_%d", i+1)} @@ -626,7 +550,7 @@ func TestBTreeCloneIsolation(t *testing.T) { // Make some changes to all three trees t.Logf("Making changes to all three trees\n") - for i := 0; i < 10000; i += 3 { + for i := 0; i < size; i += 3 { content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} tree.Delete(content) content = Content{Key: i, Value: fmt.Sprintf("Value_%d", i+1)} @@ -637,13 +561,13 @@ func TestBTreeCloneIsolation(t *testing.T) { // Check that the changes are isolated to the tree they were made in t.Logf("Checking that the changes are isolated to the tree they were made in\n") - for i := 0; i < 10000; i++ { + for i := 0; i < size; i++ { content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} val := tree.Get(content) if i%3 == 0 || i%2 == 0 { if val != nil { - t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, Content(val).Key, Content(val).Value) + t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value) } } else { if val == nil { @@ -654,7 +578,7 @@ func TestBTreeCloneIsolation(t *testing.T) { val = clone.Get(content) if i%2 != 0 || i%3 == 0 { if val != nil { - t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, Content(val).Key, Content(val).Value) + t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value) } } else { if val == nil { @@ -665,7 +589,7 @@ func TestBTreeCloneIsolation(t *testing.T) { val = clone2.Get(content) if i%2 != 0 || (i-2)%3 == 0 { if val != nil { - t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, Content(val).Key, Content(val).Value) + t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value) } } else { if val == nil { @@ -674,3 +598,83 @@ func TestBTreeCloneIsolation(t *testing.T) { } } } + +// -------------------- +// Stress tests. Disabled for testing performance + +//func TestStress(t *testing.T) { +// // Loop through creating B-Trees with a range of degrees from 3 to 12, stepping by 3. +// // Insert 1000 records into each tree, then search for each record. +// // Delete half of the records, skipping every other one, then search for each record. +// +// for degree := 3; degree <= 12; degree += 3 { +// t.Logf("Testing B-Tree of degree %d\n", degree) +// tree := New(WithDegree(degree)) +// +// // Insert 1000 records +// t.Logf("Inserting 1000 records\n") +// for i := 0; i < 1000; i++ { +// content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} +// tree.Insert(content) +// } +// +// // Search for all records +// for i := 0; i < 1000; i++ { +// content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} +// val := tree.Get(content) +// if val == nil { +// t.Errorf("Expected key %v, but didn't find it", content.Key) +// } +// } +// +// // Delete half of the records +// for i := 0; i < 1000; i += 2 { +// content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} +// tree.Delete(content) +// } +// +// // Search for all records +// for i := 0; i < 1000; i++ { +// content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} +// val := tree.Get(content) +// if i%2 == 0 { +// if val != nil { +// t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value) +// } +// } else { +// if val == nil { +// t.Errorf("Expected key %v, but didn't find it", content.Key) +// } +// } +// } +// } +// +// // Now create a very large tree, with 100000 records +// // Then delete roughly one third of them, using a very basic random number generation scheme +// // (implement it right here) to determine which records to delete. +// // Print a few lines using Logf to let the user know what's happening. +// +// t.Logf("Testing B-Tree of degree 10 with 100000 records\n") +// tree := New(WithDegree(10)) +// +// // Insert 100000 records +// t.Logf("Inserting 100000 records\n") +// for i := 0; i < 100000; i++ { +// content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)} +// tree.Insert(content) +// } +// +// // Implement a very basic random number generator +// seed := 0 +// random := func() int { +// seed = (seed*1103515245 + 12345) & 0x7fffffff +// return seed +// } +// +// // Delete one third of the records +// t.Logf("Deleting one third of the records\n") +// for i := 0; i < 35000; i++ { +// content := Content{Key: random() % 100000, Value: fmt.Sprintf("Value_%d", i)} +// tree.Delete(content) +// } +//} diff --git a/examples/gno.land/p/demo/cford32/cford32_test.gno b/examples/gno.land/p/demo/cford32/cford32_test.gno index 6f269c1b9ed..fb9373c765b 100644 --- a/examples/gno.land/p/demo/cford32/cford32_test.gno +++ b/examples/gno.land/p/demo/cford32/cford32_test.gno @@ -77,7 +77,7 @@ var bigtest = testpair{ "AHVP2WS0C9S6JV3CD5KJR831DSJ20X38CMG76V39EHM7J83MDXV6AWR", } -func testEqual(t *testing.T, msg string, args ...interface{}) bool { +func testEqual(t *testing.T, msg string, args ...any) bool { t.Helper() if args[len(args)-2] != args[len(args)-1] { t.Errorf(msg, args...) diff --git a/examples/gno.land/p/demo/context/context.gno b/examples/gno.land/p/demo/context/context.gno index 92d191012eb..7956257d700 100644 --- a/examples/gno.land/p/demo/context/context.gno +++ b/examples/gno.land/p/demo/context/context.gno @@ -10,7 +10,7 @@ package context type Context interface { // Value returns the value associated with this context for key, or nil // if no value is associated with key. - Value(key interface{}) interface{} + Value(key any) any } // Empty returns a non-nil, empty context, similar with context.Background and @@ -21,7 +21,7 @@ func Empty() Context { type emptyCtx struct{} -func (ctx emptyCtx) Value(key interface{}) interface{} { +func (ctx emptyCtx) Value(key any) any { return nil } @@ -31,17 +31,17 @@ func (ctx emptyCtx) String() string { type valueCtx struct { parent Context - key, val interface{} + key, val any } -func (ctx *valueCtx) Value(key interface{}) interface{} { +func (ctx *valueCtx) Value(key any) any { if ctx.key == key { return ctx.val } return ctx.parent.Value(key) } -func stringify(v interface{}) string { +func stringify(v any) string { switch s := v.(type) { case stringer: return s.String() @@ -63,7 +63,7 @@ func (c *valueCtx) String() string { // WithValue returns a copy of parent in which the value associated with key is // val. -func WithValue(parent Context, key, val interface{}) Context { +func WithValue(parent Context, key, val any) Context { if key == nil { panic("nil key") } diff --git a/examples/gno.land/p/demo/context/context_test.gno b/examples/gno.land/p/demo/context/context_test.gno index 0059f0d2a25..d8e2069363d 100644 --- a/examples/gno.land/p/demo/context/context_test.gno +++ b/examples/gno.land/p/demo/context/context_test.gno @@ -9,7 +9,7 @@ func TestContextExample(t *testing.T) { ctx := WithValue(Empty(), k, "Gno") if v := ctx.Value(k); v != nil { - if string(v) != "Gno" { + if v.(string) != "Gno" { t.Errorf("language value should be Gno, but is %s", v) } } else { diff --git a/examples/gno.land/p/demo/diff/diff_test.gno b/examples/gno.land/p/demo/diff/diff_test.gno index bbf4fcdf3e0..3993c91664a 100644 --- a/examples/gno.land/p/demo/diff/diff_test.gno +++ b/examples/gno.land/p/demo/diff/diff_test.gno @@ -162,12 +162,13 @@ func TestMyersDiff(t *testing.T) { new: strings.Repeat("b", 1000), expected: "[-" + strings.Repeat("a", 1000) + "][+" + strings.Repeat("b", 1000) + "]", }, - { - name: "Very long strings", - old: strings.Repeat("a", 10000) + "b" + strings.Repeat("a", 10000), - new: strings.Repeat("a", 10000) + "c" + strings.Repeat("a", 10000), - expected: strings.Repeat("a", 10000) + "[-b][+c]" + strings.Repeat("a", 10000), - }, + //{ // disabled for testing performance + // XXX: consider adding a flag to run such tests, not like `-short`, or switching to a `-bench`, maybe. + // name: "Very long strings", + // old: strings.Repeat("a", 10000) + "b" + strings.Repeat("a", 10000), + // new: strings.Repeat("a", 10000) + "c" + strings.Repeat("a", 10000), + // expected: strings.Repeat("a", 10000) + "[-b][+c]" + strings.Repeat("a", 10000), + //}, } for _, tc := range tests { diff --git a/examples/gno.land/p/demo/dom/dom.gno b/examples/gno.land/p/demo/dom/dom.gno index b4124367022..56955d7826b 100644 --- a/examples/gno.land/p/demo/dom/dom.gno +++ b/examples/gno.land/p/demo/dom/dom.gno @@ -30,7 +30,7 @@ func (plot *Plot) AddPost(title string, body string) { func (plot *Plot) String() string { str := "# [plot] " + plot.Name + "\n" if plot.Posts.Size() > 0 { - plot.Posts.Iterate("", "", func(key string, value interface{}) bool { + plot.Posts.Iterate("", "", func(key string, value any) bool { str += "\n" str += value.(*Post).String() return false @@ -50,7 +50,7 @@ func (post *Post) String() string { str += "" str += post.Body if post.Comments.Size() > 0 { - post.Comments.Iterate("", "", func(key string, value interface{}) bool { + post.Comments.Iterate("", "", func(key string, value any) bool { str += "\n" str += value.(*Comment).String() return false diff --git a/examples/gno.land/p/demo/entropy/entropy.gno b/examples/gno.land/p/demo/entropy/entropy.gno index 9e8f656c21b..850365c8236 100644 --- a/examples/gno.land/p/demo/entropy/entropy.gno +++ b/examples/gno.land/p/demo/entropy/entropy.gno @@ -57,15 +57,15 @@ func (i *Instance) addEntropy() { // handle callers { - caller1 := std.GetCallerAt(1).String() + caller1 := std.CallerAt(1).String() i.djb2String(caller1) - caller2 := std.GetCallerAt(2).String() + caller2 := std.CallerAt(2).String() i.djb2String(caller2) } // height { - height := std.GetHeight() + height := std.ChainHeight() if height >= math.MaxUint32 { height -= math.MaxUint32 } diff --git a/examples/gno.land/p/demo/flow/io_test.gno b/examples/gno.land/p/demo/flow/io_test.gno index e881854b7a1..6b64a29431f 100644 --- a/examples/gno.land/p/demo/flow/io_test.gno +++ b/examples/gno.land/p/demo/flow/io_test.gno @@ -9,7 +9,7 @@ import ( "testing" "time" - ios_test "internal/os_test" + ios_test "os_test" ) // XXX ugh, I can't even sleep milliseconds. diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno index eea4782909e..0d4b727f5f5 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno @@ -106,7 +106,7 @@ type PostMessageHandler interface { // TODO: Consider further message types that could allow administrative action such as modifying // a feed's whitelist without the owner of this oracle having to maintain a reference to it. func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) (string, error) { - caller := string(std.GetOrigCaller()) + caller := string(std.OriginCaller()) funcType, msg := message.ParseFunc(msg) @@ -193,7 +193,7 @@ func (i *Instance) GetFeedDefinitions(forAddress string) (string, error) { var err error // The boolean value returned by this callback function indicates whether to stop iterating. - i.feeds.Iterate("", "", func(_ string, value interface{}) bool { + i.feeds.Iterate("", "", func(_ string, value any) bool { feedWithWhitelist, ok := value.(FeedWithWhitelist) if !ok { err = errors.New("invalid feed type") diff --git a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno index f152ee90e79..1ed5f93d5fd 100644 --- a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno +++ b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token.gno @@ -62,7 +62,7 @@ func (s *basicGRC1155Token) SetApprovalForAll(operator std.Address, approved boo return ErrInvalidAddress } - caller := std.GetOrigCaller() + caller := std.OriginCaller() return s.setApprovalForAll(caller, operator, approved) } @@ -85,7 +85,7 @@ func (s *basicGRC1155Token) IsApprovedForAll(owner, operator std.Address) bool { // contract recipients are aware of the GRC1155 protocol to prevent // tokens from being forever locked. func (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, amount uint64) error { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if !s.IsApprovedForAll(caller, from) { return ErrCallerIsNotOwnerOrApproved } @@ -108,7 +108,7 @@ func (s *basicGRC1155Token) SafeTransferFrom(from, to std.Address, tid TokenID, // contract recipients are aware of the GRC1155 protocol to prevent // tokens from being forever locked. func (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch []TokenID, amounts []uint64) error { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if !s.IsApprovedForAll(caller, from) { return ErrCallerIsNotOwnerOrApproved } @@ -130,7 +130,7 @@ func (s *basicGRC1155Token) SafeBatchTransferFrom(from, to std.Address, batch [] // Creates `amount` tokens of token type `id`, and assigns them to `to`. Also checks that // contract recipients are using GRC1155 protocol. func (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) error { - caller := std.GetOrigCaller() + caller := std.OriginCaller() err := s.mintBatch(to, []TokenID{tid}, []uint64{amount}) if err != nil { @@ -149,7 +149,7 @@ func (s *basicGRC1155Token) SafeMint(to std.Address, tid TokenID, amount uint64) // Batch version of `SafeMint()`. Also checks that // contract recipients are using GRC1155 protocol. func (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amounts []uint64) error { - caller := std.GetOrigCaller() + caller := std.OriginCaller() err := s.mintBatch(to, batch, amounts) if err != nil { @@ -167,7 +167,7 @@ func (s *basicGRC1155Token) SafeBatchMint(to std.Address, batch []TokenID, amoun // Destroys `amount` tokens of token type `id` from `from`. func (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) error { - caller := std.GetOrigCaller() + caller := std.OriginCaller() err := s.burnBatch(from, []TokenID{tid}, []uint64{amount}) if err != nil { @@ -181,7 +181,7 @@ func (s *basicGRC1155Token) Burn(from std.Address, tid TokenID, amount uint64) e // Batch version of `Burn()` func (s *basicGRC1155Token) BatchBurn(from std.Address, batch []TokenID, amounts []uint64) error { - caller := std.GetOrigCaller() + caller := std.OriginCaller() err := s.burnBatch(from, batch, amounts) if err != nil { @@ -225,7 +225,7 @@ func (s *basicGRC1155Token) safeBatchTransferFrom(from, to std.Address, batch [] return ErrCannotTransferToSelf } - caller := std.GetOrigCaller() + caller := std.OriginCaller() s.beforeTokenTransfer(caller, from, to, batch, amounts) for i := 0; i < len(batch); i++ { @@ -265,7 +265,7 @@ func (s *basicGRC1155Token) mintBatch(to std.Address, batch []TokenID, amounts [ return ErrInvalidAddress } - caller := std.GetOrigCaller() + caller := std.OriginCaller() s.beforeTokenTransfer(caller, zeroAddress, to, batch, amounts) for i := 0; i < len(batch); i++ { @@ -294,7 +294,7 @@ func (s *basicGRC1155Token) burnBatch(from std.Address, batch []TokenID, amounts return ErrInvalidAddress } - caller := std.GetOrigCaller() + caller := std.OriginCaller() s.beforeTokenTransfer(caller, from, zeroAddress, batch, amounts) for i := 0; i < len(batch); i++ { diff --git a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno index 2fef3431b43..4092af72037 100644 --- a/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno +++ b/examples/gno.land/p/demo/grc/grc1155/basic_grc1155_token_test.gno @@ -30,7 +30,7 @@ func TestBalanceOf(t *testing.T) { tid1 := TokenID("1") tid2 := TokenID("2") - balanceZeroAddressOfToken1, err := dummy.BalanceOf(zeroAddress, tid1) + _, err := dummy.BalanceOf(zeroAddress, tid1) uassert.Error(t, err, "should result in error") balanceAddr1OfToken1, err := dummy.BalanceOf(addr1, tid1) @@ -91,7 +91,7 @@ func TestSetApprovalForAll(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) uassert.True(t, dummy != nil, "should not be nil") - caller := std.GetOrigCaller() + caller := std.OriginCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") isApprovedForAll := dummy.IsApprovedForAll(caller, addr) @@ -114,7 +114,7 @@ func TestSafeTransferFrom(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) uassert.True(t, dummy != nil, "should not be nil") - caller := std.GetOrigCaller() + caller := std.OriginCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") tid := TokenID("1") @@ -145,7 +145,7 @@ func TestSafeBatchTransferFrom(t *testing.T) { dummy := NewBasicGRC1155Token(dummyURI) uassert.True(t, dummy != nil, "should not be nil") - caller := std.GetOrigCaller() + caller := std.OriginCaller() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") tid1 := TokenID("1") diff --git a/examples/gno.land/p/demo/grc/grc1155/util.gno b/examples/gno.land/p/demo/grc/grc1155/util.gno index 2c6452a1066..b8d22803836 100644 --- a/examples/gno.land/p/demo/grc/grc1155/util.gno +++ b/examples/gno.land/p/demo/grc/grc1155/util.gno @@ -13,6 +13,6 @@ func isValidAddress(addr std.Address) bool { return true } -func emit(event interface{}) { +func emit(event any) { // TODO: setup a pubsub system here? } diff --git a/examples/gno.land/p/demo/grc/grc20/examples_test.gno b/examples/gno.land/p/demo/grc/grc20/examples_test.gno index 6a2bfa11d8c..17ceb240668 100644 --- a/examples/gno.land/p/demo/grc/grc20/examples_test.gno +++ b/examples/gno.land/p/demo/grc/grc20/examples_test.gno @@ -7,7 +7,7 @@ func ExampleExposeBankForMaketxRunOrImports() {} func ExampleCustomTellerImpl() {} func ExampleAllowance() {} func ExampleRealmBanker() {} -func ExamplePrevRealmBanker() {} +func ExamplePreviousRealmBanker() {} func ExampleAccountBanker() {} func ExampleTransfer() {} func ExampleApprove() {} diff --git a/examples/gno.land/p/demo/grc/grc20/tellers.gno b/examples/gno.land/p/demo/grc/grc20/tellers.gno index ee5d2d7fcca..733d10148e3 100644 --- a/examples/gno.land/p/demo/grc/grc20/tellers.gno +++ b/examples/gno.land/p/demo/grc/grc20/tellers.gno @@ -4,7 +4,7 @@ import ( "std" ) -// CallerTeller returns a GRC20 compatible teller that checks the PrevRealm +// CallerTeller returns a GRC20 compatible teller that checks the PreviousRealm // caller for each call. It's usually safe to expose it publicly to let users // manipulate their tokens directly, or for realms to use their allowance. func (tok *Token) CallerTeller() Teller { @@ -14,7 +14,7 @@ func (tok *Token) CallerTeller() Teller { return &fnTeller{ accountFn: func() std.Address { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() return caller }, Token: tok, @@ -44,7 +44,7 @@ func (tok *Token) RealmTeller() Teller { panic("Token cannot be nil") } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() return &fnTeller{ accountFn: func() std.Address { @@ -61,7 +61,7 @@ func (tok *Token) RealmSubTeller(slug string) Teller { panic("Token cannot be nil") } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() account := accountSlugAddr(caller, slug) return &fnTeller{ diff --git a/examples/gno.land/p/demo/grc/grc20/tellers_test.gno b/examples/gno.land/p/demo/grc/grc20/tellers_test.gno index 2a724964edc..3175359df85 100644 --- a/examples/gno.land/p/demo/grc/grc20/tellers_test.gno +++ b/examples/gno.land/p/demo/grc/grc20/tellers_test.gno @@ -115,12 +115,12 @@ func TestCallerTeller(t *testing.T) { checkBalances(1000, 0, 0) checkAllowances(0, 0, 0, 0, 0, 0) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) urequire.NoError(t, teller.Approve(bob, 600)) checkBalances(1000, 0, 0) checkAllowances(600, 0, 0, 0, 0, 0) - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) urequire.Error(t, teller.TransferFrom(alice, carl, 700)) checkBalances(1000, 0, 0) checkAllowances(600, 0, 0, 0, 0, 0) 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 0505aaa1c26..ed7f96dd598 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno @@ -81,7 +81,7 @@ func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { if err != nil { return false, err } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if caller != owner { return false, ErrCallerIsNotOwner } @@ -115,7 +115,7 @@ func (s *basicNFT) Approve(to std.Address, tid TokenID) error { return ErrApprovalToCurrentOwner } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if caller != owner && !s.IsApprovedForAll(owner, caller) { return ErrCallerIsNotOwnerOrApproved } @@ -147,7 +147,7 @@ func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error return ErrInvalidAddress } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() return s.setApprovalForAll(caller, operator, approved) } @@ -155,7 +155,7 @@ func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error // contract recipients are aware of the GRC721 protocol to prevent // tokens from being forever locked. func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if !s.isApprovedOrOwner(caller, tid) { return ErrCallerIsNotOwnerOrApproved } @@ -174,7 +174,7 @@ func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { // Transfers `tokenId` token from `from` to `to`. func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if !s.isApprovedOrOwner(caller, tid) { return ErrCallerIsNotOwnerOrApproved } 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 6375b0307a8..ed45373684a 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 @@ -111,7 +111,7 @@ func TestSetApprovalForAll(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") isApprovedForAll := dummy.IsApprovedForAll(caller, addr) @@ -128,7 +128,7 @@ func TestGetApproved(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - approvedAddr, err := dummy.GetApproved(TokenID("invalid")) + _, err := dummy.GetApproved(TokenID("invalid")) uassert.Error(t, err, "should result in error") } @@ -136,7 +136,7 @@ func TestApprove(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") dummy.mint(caller, TokenID("1")) @@ -156,7 +156,7 @@ func TestTransferFrom(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") dummy.mint(caller, TokenID("1")) @@ -185,7 +185,7 @@ func TestSafeTransferFrom(t *testing.T) { dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) uassert.True(t, dummy != nil, "should not be nil") - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") dummy.mint(caller, TokenID("1")) @@ -247,7 +247,7 @@ func TestBurn(t *testing.T) { uassert.NoError(t, err, "should not result in error") // Check Owner of Token id - owner, err := dummy.OwnerOf(TokenID("1")) + _, err = dummy.OwnerOf(TokenID("1")) uassert.Error(t, err, "should result in error") } @@ -259,7 +259,7 @@ func TestSetTokenURI(t *testing.T) { addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") tokenURI := "http://example.com/token" - std.TestSetOrigCaller(std.Address(addr1)) // addr1 + std.TestSetOriginCaller(std.Address(addr1)) // addr1 dummy.mint(addr1, TokenID("1")) _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) @@ -269,13 +269,13 @@ func TestSetTokenURI(t *testing.T) { _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) uassert.ErrorIs(t, err, ErrInvalidTokenId) - std.TestSetOrigCaller(std.Address(addr2)) // addr2 + std.TestSetOriginCaller(std.Address(addr2)) // addr2 _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) // Test case: Retrieving TokenURI - std.TestSetOrigCaller(std.Address(addr1)) // addr1 + std.TestSetOriginCaller(std.Address(addr1)) // addr1 dummyTokenURI, err := dummy.TokenURI(TokenID("1")) uassert.NoError(t, err, "TokenURI error") diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno index 05fad41be18..2b408a206ed 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno @@ -29,16 +29,6 @@ func NewNFTWithMetadata(name string, symbol string) *metadataNFT { // SetTokenMetadata sets metadata for a given token ID. func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { - // Check if the caller is the owner of the token - owner, err := s.basicNFT.OwnerOf(tid) - if err != nil { - return err - } - caller := std.PrevRealm().Addr() - if caller != owner { - return ErrCallerIsNotOwner - } - // Set the metadata for the token ID in the extensions AVL tree s.extensions.Set(string(tid), metadata) return nil @@ -55,44 +45,72 @@ func (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) { return metadata.(Metadata), nil } -// mint mints a new token and assigns it to the specified address. -func (s *metadataNFT) mint(to std.Address, tid TokenID) error { - // Check if the address is valid - if err := isValidAddress(to); err != nil { - return err - } +// Basic NFT methods forwarded to embedded basicNFT - // Check if the token ID already exists - if s.basicNFT.exists(tid) { - return ErrTokenIdAlreadyExists - } +func (s *metadataNFT) Name() string { + return s.basicNFT.Name() +} - s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) +func (s *metadataNFT) Symbol() string { + return s.basicNFT.Symbol() +} - // Check if the token ID was minted by beforeTokenTransfer - if s.basicNFT.exists(tid) { - return ErrTokenIdAlreadyExists - } +func (s *metadataNFT) TokenCount() uint64 { + return s.basicNFT.TokenCount() +} - // Increment balance of the recipient address - toBalance, err := s.basicNFT.BalanceOf(to) - if err != nil { - return err - } - toBalance += 1 - s.basicNFT.balances.Set(to.String(), toBalance) +func (s *metadataNFT) BalanceOf(addr std.Address) (uint64, error) { + return s.basicNFT.BalanceOf(addr) +} + +func (s *metadataNFT) OwnerOf(tid TokenID) (std.Address, error) { + return s.basicNFT.OwnerOf(tid) +} - // Set owner of the token ID to the recipient address - s.basicNFT.owners.Set(string(tid), to) +func (s *metadataNFT) TokenURI(tid TokenID) (string, error) { + return s.basicNFT.TokenURI(tid) +} - std.Emit( - TransferEvent, - "from", string(zeroAddress), - "to", string(to), - "tokenId", string(tid), - ) +func (s *metadataNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { + return s.basicNFT.SetTokenURI(tid, tURI) +} - s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) +func (s *metadataNFT) IsApprovedForAll(owner, operator std.Address) bool { + return s.basicNFT.IsApprovedForAll(owner, operator) +} - return nil +func (s *metadataNFT) Approve(to std.Address, tid TokenID) error { + return s.basicNFT.Approve(to, tid) +} + +func (s *metadataNFT) GetApproved(tid TokenID) (std.Address, error) { + return s.basicNFT.GetApproved(tid) +} + +func (s *metadataNFT) SetApprovalForAll(operator std.Address, approved bool) error { + return s.basicNFT.SetApprovalForAll(operator, approved) +} + +func (s *metadataNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { + return s.basicNFT.SafeTransferFrom(from, to, tid) +} + +func (s *metadataNFT) TransferFrom(from, to std.Address, tid TokenID) error { + return s.basicNFT.TransferFrom(from, to, tid) +} + +func (s *metadataNFT) Mint(to std.Address, tid TokenID) error { + return s.basicNFT.Mint(to, tid) +} + +func (s *metadataNFT) SafeMint(to std.Address, tid TokenID) error { + return s.basicNFT.SafeMint(to, tid) +} + +func (s *metadataNFT) Burn(tid TokenID) error { + return s.basicNFT.Burn(tid) +} + +func (s *metadataNFT) RenderHome() string { + return s.basicNFT.RenderHome() } diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno index ad002a7c98e..b1de830fb36 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno @@ -31,7 +31,7 @@ func TestSetMetadata(t *testing.T) { youtubeURL := "test" // Set the original caller to addr1 - std.TestSetOrigCaller(addr1) // addr1 + std.TestSetOriginCaller(addr1) // addr1 // Mint a new token for addr1 dummy.mint(addr1, TokenID("1")) @@ -69,7 +69,7 @@ func TestSetMetadata(t *testing.T) { uassert.ErrorIs(t, err, ErrInvalidTokenId) // Set the original caller to addr2 - std.TestSetOrigCaller(addr2) // addr2 + std.TestSetOriginCaller(addr2) // addr2 // Try to set metadata for token 1 from addr2 (should fail) cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ @@ -88,7 +88,7 @@ func TestSetMetadata(t *testing.T) { uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) // Set the original caller back to addr1 - std.TestSetOrigCaller(addr1) // addr1 + std.TestSetOriginCaller(addr1) // addr1 // Retrieve metadata for token 1 dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno index 9831c709121..df13ae76d20 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno @@ -45,7 +45,7 @@ func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error if err != nil { return err } - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if caller != owner { return ErrCallerIsNotOwner } diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno index 7893453a1c6..7d732854a7c 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno @@ -21,7 +21,7 @@ func TestSetTokenRoyalty(t *testing.T) { salePrice := uint64(1000) expectRoyaltyAmount := uint64(100) - std.TestSetOrigCaller(addr1) // addr1 + std.TestSetOriginCaller(addr1) // addr1 dummy.mint(addr1, TokenID("1")) @@ -32,13 +32,13 @@ func TestSetTokenRoyalty(t *testing.T) { uassert.NoError(t, derr, "Should not result in error") // Test case: Invalid token ID - err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + _ = dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ PaymentAddress: paymentAddress, Percentage: percentage, }) uassert.ErrorIs(t, derr, ErrInvalidTokenId) - std.TestSetOrigCaller(addr2) // addr2 + std.TestSetOriginCaller(addr2) // addr2 cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ PaymentAddress: paymentAddress, @@ -61,7 +61,7 @@ func TestSetTokenRoyalty(t *testing.T) { uassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage) // Test case: Retrieving Royalty Info - std.TestSetOrigCaller(addr1) // addr1 + std.TestSetOriginCaller(addr1) // addr1 dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) uassert.NoError(t, rerr, "RoyaltyInfo error") diff --git a/examples/gno.land/p/demo/grc/grc721/util.gno b/examples/gno.land/p/demo/grc/grc721/util.gno index bb6bf24d984..e5cc8d51b3b 100644 --- a/examples/gno.land/p/demo/grc/grc721/util.gno +++ b/examples/gno.land/p/demo/grc/grc721/util.gno @@ -13,6 +13,6 @@ func isValidAddress(addr std.Address) error { return nil } -func emit(event interface{}) { +func emit(event any) { // TODO: setup a pubsub system here? } diff --git a/examples/gno.land/p/demo/grc/grc777/dummy_test.gno b/examples/gno.land/p/demo/grc/grc777/dummy_test.gno index 5f8ac3598d1..33415fc689a 100644 --- a/examples/gno.land/p/demo/grc/grc777/dummy_test.gno +++ b/examples/gno.land/p/demo/grc/grc777/dummy_test.gno @@ -11,7 +11,7 @@ type dummyImpl struct{} var _ IGRC777 = (*dummyImpl)(nil) func TestInterface(t *testing.T) { - var dummy IGRC777 = &dummyImpl{} + var _ IGRC777 = &dummyImpl{} } func (impl *dummyImpl) GetName() string { panic("not implemented") } diff --git a/examples/gno.land/p/demo/json/buffer.gno b/examples/gno.land/p/demo/json/buffer.gno index a217ee653f9..91f7a8778cd 100644 --- a/examples/gno.land/p/demo/json/buffer.gno +++ b/examples/gno.land/p/demo/json/buffer.gno @@ -428,7 +428,7 @@ func (b *buffer) word(bs []byte) error { return nil } -func numberKind2f64(value interface{}) (result float64, err error) { +func numberKind2f64(value any) (result float64, err error) { switch typed := value.(type) { case float64: result = typed diff --git a/examples/gno.land/p/demo/json/encode_test.gno b/examples/gno.land/p/demo/json/encode_test.gno index 831a9e0e0a2..5d8dfdeb47f 100644 --- a/examples/gno.land/p/demo/json/encode_test.gno +++ b/examples/gno.land/p/demo/json/encode_test.gno @@ -98,7 +98,7 @@ func TestMarshal_Object(t *testing.T) { } } -func valueNode(prev *Node, key string, typ ValueType, val interface{}) *Node { +func valueNode(prev *Node, key string, typ ValueType, val any) *Node { curr := &Node{ prev: prev, data: nil, diff --git a/examples/gno.land/p/demo/json/node.gno b/examples/gno.land/p/demo/json/node.gno index c917150bc3d..010063eeb6f 100644 --- a/examples/gno.land/p/demo/json/node.gno +++ b/examples/gno.land/p/demo/json/node.gno @@ -14,7 +14,7 @@ type Node struct { next map[string]*Node // next is the child nodes of the current node. key *string // key holds the key of the current node in the parent node. data []byte // byte slice of JSON data - value interface{} // value holds the value of the current node. + value any // value holds the value of the current node. nodeType ValueType // NodeType holds the type of the current node. (Object, Array, String, Number, Boolean, Null) index *int // index holds the index of the current node in the parent array node. borders [2]int // borders stores the start and end index of the current node in the data. @@ -57,7 +57,7 @@ func NewNode(prev *Node, b *buffer, typ ValueType, key **string) (*Node, error) } // load retrieves the value of the current node. -func (n *Node) load() interface{} { +func (n *Node) load() any { return n.value } @@ -165,7 +165,7 @@ func (n *Node) Type() ValueType { // } // // result: "value" -func (n *Node) Value() (value interface{}, err error) { +func (n *Node) Value() (value any, err error) { value = n.load() if value == nil { @@ -554,7 +554,7 @@ func (n *Node) root() *Node { // if val != nil { // t.Errorf("GetNull returns wrong result: %v", val) // } -func (n *Node) GetNull() (interface{}, error) { +func (n *Node) GetNull() (any, error) { if n == nil { return nil, errNilNode } @@ -569,7 +569,7 @@ func (n *Node) GetNull() (interface{}, error) { // MustNull returns the null value if current node is null type. // // It panics if the current node is not null type. -func (n *Node) MustNull() interface{} { +func (n *Node) MustNull() any { v, err := n.GetNull() if err != nil { panic(err) diff --git a/examples/gno.land/p/demo/json/node_test.gno b/examples/gno.land/p/demo/json/node_test.gno index dbc82369f68..7546bf2e4cc 100644 --- a/examples/gno.land/p/demo/json/node_test.gno +++ b/examples/gno.land/p/demo/json/node_test.gno @@ -85,7 +85,7 @@ func TestNode_Value(t *testing.T) { name string data []byte _type ValueType - expected interface{} + expected any errExpected bool }{ {name: "null", data: []byte("null"), _type: Null, expected: nil}, @@ -285,7 +285,7 @@ func TestNode_GetBool(t *testing.T) { } } -func TestNode_GetBool_Fail(t *testing.T) { +func TestNode_GetBool_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"literally null node", NullNode("")}, @@ -357,7 +357,7 @@ func TestNode_GetNull(t *testing.T) { } } -func TestNode_GetNull_Fail(t *testing.T) { +func TestNode_GetNull_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"number node is null", NumberNode("", 42)}, @@ -435,7 +435,7 @@ func TestNode_GetNumeric_With_Unmarshal(t *testing.T) { } } -func TestNode_GetNumeric_Fail(t *testing.T) { +func TestNode_GetNumeric_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"null node", NullNode("")}, @@ -467,7 +467,7 @@ func TestNode_GetString(t *testing.T) { } } -func TestNode_GetString_Fail(t *testing.T) { +func TestNode_GetString_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"null node", NullNode("")}, @@ -577,7 +577,7 @@ func TestNode_GetArray(t *testing.T) { } } -func TestNode_GetArray_Fail(t *testing.T) { +func TestNode_GetArray_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"null node", NullNode("")}, @@ -736,7 +736,7 @@ func TestNode_Index(t *testing.T) { } } -func TestNode_Index_Fail(t *testing.T) { +func TestNode_Index_NotSucceed(t *testing.T) { tests := []struct { name string node *Node @@ -854,7 +854,7 @@ func TestNode_GetKey(t *testing.T) { } } -func TestNode_GetKey_Fail(t *testing.T) { +func TestNode_GetKey_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"null node", NullNode("")}, @@ -998,7 +998,7 @@ func TestNode_GetObject(t *testing.T) { } } -func TestNode_GetObject_Fail(t *testing.T) { +func TestNode_GetObject_NotSucceed(t *testing.T) { tests := []simpleNode{ {"nil node", (*Node)(nil)}, {"get object from null node", NullNode("")}, diff --git a/examples/gno.land/p/demo/membstore/membstore.gno b/examples/gno.land/p/demo/membstore/membstore.gno index ca721d078e6..b19c6404105 100644 --- a/examples/gno.land/p/demo/membstore/membstore.gno +++ b/examples/gno.land/p/demo/membstore/membstore.gno @@ -175,7 +175,7 @@ func (m *MembStore) Members(offset, count uint64) []Member { m.members.IterateByOffset( int(offset), int(count), - func(_ string, val interface{}) bool { + func(_ string, val any) bool { member := val.(Member) // Save the member diff --git a/examples/gno.land/p/demo/memeland/memeland.gno b/examples/gno.land/p/demo/memeland/memeland.gno index 38f42239bec..55a8dbb27b1 100644 --- a/examples/gno.land/p/demo/memeland/memeland.gno +++ b/examples/gno.land/p/demo/memeland/memeland.gno @@ -50,7 +50,7 @@ func (m *Memeland) PostMeme(data string, timestamp int64) string { newPost := &Post{ ID: id, Data: data, - Author: std.PrevRealm().Addr(), + Author: std.PreviousRealm().Address(), Timestamp: time.Unix(timestamp, 0), UpvoteTracker: avl.NewTree(), } @@ -65,7 +65,7 @@ func (m *Memeland) Upvote(id string) string { panic("post with specified ID does not exist") } - caller := std.PrevRealm().Addr().String() + caller := std.PreviousRealm().Address().String() if _, exists := post.UpvoteTracker.Get(caller); exists { panic("user has already upvoted this post") diff --git a/examples/gno.land/p/demo/memeland/memeland_test.gno b/examples/gno.land/p/demo/memeland/memeland_test.gno index 95065b8cd64..bf1084a6f55 100644 --- a/examples/gno.land/p/demo/memeland/memeland_test.gno +++ b/examples/gno.land/p/demo/memeland/memeland_test.gno @@ -122,7 +122,7 @@ func TestGetPostsInRangeByUpvote(t *testing.T) { m.Upvote(id2) // Change caller so avoid double upvote panic - std.TestSetOrigCaller(testutils.TestAddress("alice")) + std.TestSetOriginCaller(testutils.TestAddress("alice")) m.Upvote(id1) // Final upvote count: @@ -236,21 +236,21 @@ func TestUpvote(t *testing.T) { func TestDelete(t *testing.T) { alice := testutils.TestAddress("alice") - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) // Alice is admin m := NewMemeland() // Set caller to Bob bob := testutils.TestAddress("bob") - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) // Bob adds post to Memeland now := time.Now() postID := m.PostMeme("Meme #1", now.Unix()) // Alice removes Bob's post - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) id := m.RemovePost(postID) uassert.Equal(t, postID, id, "post IDs not matching") @@ -259,7 +259,7 @@ func TestDelete(t *testing.T) { func TestDeleteByNonAdmin(t *testing.T) { alice := testutils.TestAddress("alice") - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) m := NewMemeland() @@ -269,7 +269,7 @@ func TestDeleteByNonAdmin(t *testing.T) { // Bob will try to delete meme posted by Alice, which should fail bob := testutils.TestAddress("bob") - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) defer func() { if r := recover(); r == nil { diff --git a/examples/gno.land/p/demo/microblog/microblog.gno b/examples/gno.land/p/demo/microblog/microblog.gno index f6d6709f20b..7d30b066e97 100644 --- a/examples/gno.land/p/demo/microblog/microblog.gno +++ b/examples/gno.land/p/demo/microblog/microblog.gno @@ -36,7 +36,7 @@ func (m *Microblog) GetPages() []*Page { index = 0 ) - m.Pages.Iterate("", "", func(key string, value interface{}) bool { + m.Pages.Iterate("", "", func(key string, value any) bool { pages[index] = value.(*Page) index++ return false @@ -48,7 +48,7 @@ func (m *Microblog) GetPages() []*Page { } func (m *Microblog) NewPost(text string) error { - author := std.GetOrigCaller() + author := std.OriginCaller() _, found := m.Pages.Get(author.String()) if !found { // make a new page for the new author @@ -103,7 +103,7 @@ func (p *Page) NewPost(text string) error { func (p *Page) GetPosts() []*Post { posts := make([]*Post, p.Posts.Size()) i := 0 - p.Posts.ReverseIterate("", "", func(key string, value interface{}) bool { + p.Posts.ReverseIterate("", "", func(key string, value any) bool { postParsed := value.(*Post) posts[i] = postParsed i++ diff --git a/examples/gno.land/p/demo/mux/handler.gno b/examples/gno.land/p/demo/mux/handler.gno index 835d050a52c..4d937dbacab 100644 --- a/examples/gno.land/p/demo/mux/handler.gno +++ b/examples/gno.land/p/demo/mux/handler.gno @@ -7,6 +7,8 @@ type Handler struct { type HandlerFunc func(*ResponseWriter, *Request) -// TODO: type ErrHandlerFunc func(*ResponseWriter, *Request) error -// TODO: NotFoundHandler +type ErrHandlerFunc func(*ResponseWriter, *Request) error + +type NotFoundHandler func(*ResponseWriter, *Request) + // TODO: AutomaticIndex diff --git a/examples/gno.land/p/demo/mux/request.gno b/examples/gno.land/p/demo/mux/request.gno index 7b5b74da91b..eaa2f287069 100644 --- a/examples/gno.land/p/demo/mux/request.gno +++ b/examples/gno.land/p/demo/mux/request.gno @@ -18,24 +18,29 @@ type Request struct { // GetVar retrieves a variable from the path based on routing rules. func (r *Request) GetVar(key string) string { - var ( - handlerParts = strings.Split(r.HandlerPath, "/") - reqParts = strings.Split(r.Path, "/") - ) - - for i := 0; i < len(handlerParts); i++ { - handlerPart := handlerParts[i] + handlerParts := strings.Split(r.HandlerPath, "/") + reqParts := strings.Split(r.Path, "/") + reqIndex := 0 + for handlerIndex := 0; handlerIndex < len(handlerParts); handlerIndex++ { + handlerPart := handlerParts[handlerIndex] switch { case handlerPart == "*": - // XXX: implement a/b/*/d/e - panic("not implemented") + // If a wildcard "*" is found, consume all remaining segments + wildcardParts := reqParts[reqIndex:] + reqIndex = len(reqParts) // Consume all remaining segments + return strings.Join(wildcardParts, "/") // Return all remaining segments as a string case strings.HasPrefix(handlerPart, "{") && strings.HasSuffix(handlerPart, "}"): + // If a variable of the form {param} is found we compare it with the key parameter := handlerPart[1 : len(handlerPart)-1] if parameter == key { - return reqParts[i] + return reqParts[reqIndex] } + reqIndex++ default: - // continue + if reqIndex >= len(reqParts) || handlerPart != reqParts[reqIndex] { + return "" + } + reqIndex++ } } diff --git a/examples/gno.land/p/demo/mux/request_test.gno b/examples/gno.land/p/demo/mux/request_test.gno index 5f8088b4964..24c611c1f9d 100644 --- a/examples/gno.land/p/demo/mux/request_test.gno +++ b/examples/gno.land/p/demo/mux/request_test.gno @@ -1,8 +1,10 @@ package mux import ( - "fmt" "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" ) func TestRequest_GetVar(t *testing.T) { @@ -12,28 +14,35 @@ func TestRequest_GetVar(t *testing.T) { getVarKey string expectedOutput string }{ + {"users/{id}", "users/123", "id", "123"}, {"users/123", "users/123", "id", ""}, {"users/{id}", "users/123", "nonexistent", ""}, - {"a/{b}/c/{d}", "a/42/c/1337", "b", "42"}, - {"a/{b}/c/{d}", "a/42/c/1337", "d", "1337"}, - {"{a}", "foo", "a", "foo"}, - // TODO: wildcards: a/*/c - // TODO: multiple patterns per slashes: a/{b}-{c}/d - } + {"users/{userId}/posts/{postId}", "users/123/posts/456", "userId", "123"}, + {"users/{userId}/posts/{postId}", "users/123/posts/456", "postId", "456"}, + + // Wildcards + {"*", "users/123", "*", "users/123"}, + {"*", "users/123/posts/456", "*", "users/123/posts/456"}, + {"*", "users/123/posts/456/comments/789", "*", "users/123/posts/456/comments/789"}, + {"users/*", "users/john/posts", "*", "john/posts"}, + {"users/*/comments", "users/jane/comments", "*", "jane/comments"}, + {"api/*/posts/*", "api/v1/posts/123", "*", "v1/posts/123"}, + // wildcards and parameters + {"api/{version}/*", "api/v1/user/settings", "version", "v1"}, + } for _, tt := range cases { - name := fmt.Sprintf("%s-%s", tt.handlerPath, tt.reqPath) + name := ufmt.Sprintf("%s-%s", tt.handlerPath, tt.reqPath) t.Run(name, func(t *testing.T) { req := &Request{ HandlerPath: tt.handlerPath, Path: tt.reqPath, } - output := req.GetVar(tt.getVarKey) - if output != tt.expectedOutput { - t.Errorf("Expected '%q, but got %q", tt.expectedOutput, output) - } + uassert.Equal(t, tt.expectedOutput, output, + "handler: %q, path: %q, key: %q", + tt.handlerPath, tt.reqPath, tt.getVarKey) }) } } diff --git a/examples/gno.land/p/demo/mux/router.gno b/examples/gno.land/p/demo/mux/router.gno index fe6bf70abdf..4fca43a0378 100644 --- a/examples/gno.land/p/demo/mux/router.gno +++ b/examples/gno.land/p/demo/mux/router.gno @@ -5,7 +5,7 @@ import "strings" // Router handles the routing and rendering logic. type Router struct { routes []Handler - NotFoundHandler HandlerFunc + NotFoundHandler NotFoundHandler } // NewRouter creates a new Router instance. @@ -23,8 +23,14 @@ func (r *Router) Render(reqPath string) string { for _, route := range r.routes { patParts := strings.Split(route.Pattern, "/") - - if len(patParts) != len(reqParts) { + wildcard := false + for _, part := range patParts { + if part == "*" { + wildcard = true + break + } + } + if !wildcard && len(patParts) != len(reqParts) { continue } @@ -34,7 +40,7 @@ func (r *Router) Render(reqPath string) string { reqPart := reqParts[i] if patPart == "*" { - continue + break } if strings.HasPrefix(patPart, "{") && strings.HasSuffix(patPart, "}") { continue @@ -63,12 +69,31 @@ func (r *Router) Render(reqPath string) string { return res.Output() } -// Handle registers a route and its handler function. +// HandleFunc registers a route and its handler function. func (r *Router) HandleFunc(pattern string, fn HandlerFunc) { route := Handler{Pattern: pattern, Fn: fn} r.routes = append(r.routes, route) } +// HandleErrFunc registers a route and its error handler function. +func (r *Router) HandleErrFunc(pattern string, fn ErrHandlerFunc) { + + // Convert ErrHandlerFunc to regular HandlerFunc + handler := func(res *ResponseWriter, req *Request) { + if err := fn(res, req); err != nil { + res.Write("Error: " + err.Error()) + } + } + + r.HandleFunc(pattern, handler) +} + +// SetNotFoundHandler sets custom message for 404 defaultNotFoundHandler. +func (r *Router) SetNotFoundHandler(handler NotFoundHandler) { + r.NotFoundHandler = handler +} + +// stripQueryString removes query string from the request path. func stripQueryString(reqPath string) string { i := strings.Index(reqPath, "?") if i == -1 { diff --git a/examples/gno.land/p/demo/mux/router_test.gno b/examples/gno.land/p/demo/mux/router_test.gno index cc6aad62146..c1c5d218165 100644 --- a/examples/gno.land/p/demo/mux/router_test.gno +++ b/examples/gno.land/p/demo/mux/router_test.gno @@ -72,7 +72,33 @@ func TestRouter_Render(t *testing.T) { }) }, }, - + { + label: "wildcard in route", + path: "hello/Alice/Bob", + expectedOutput: "Matched: Alice/Bob", + setupHandler: func(t *testing.T, r *Router) { + r.HandleFunc("hello/*", func(rw *ResponseWriter, req *Request) { + path := req.GetVar("*") + uassert.Equal(t, "Alice/Bob", path) + uassert.Equal(t, "hello/Alice/Bob", req.Path) + rw.Write("Matched: " + path) + }) + }, + }, + { + label: "wildcard in route with query string", + path: "hello/Alice/Bob?foo=bar", + expectedOutput: "Matched: Alice/Bob", + setupHandler: func(t *testing.T, r *Router) { + r.HandleFunc("hello/*", func(rw *ResponseWriter, req *Request) { + path := req.GetVar("*") + uassert.Equal(t, "Alice/Bob", path) + uassert.Equal(t, "hello/Alice/Bob?foo=bar", req.RawPath) + uassert.Equal(t, "hello/Alice/Bob", req.Path) + rw.Write("Matched: " + path) + }) + }, + }, // TODO: {"hello", "Hello, world!"}, // TODO: hello/, /hello, hello//Alice, hello/Alice/, hello/Alice/Bob, etc } diff --git a/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno b/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno index 4c489f430f9..9ed8f1a155c 100644 --- a/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno +++ b/examples/gno.land/p/demo/nestedpkg/nestedpkg.gno @@ -2,10 +2,10 @@ // It is useful for upgrade patterns relying on namespaces. package nestedpkg -// To test this from a realm and have std.CurrentRealm/PrevRealm work correctly, +// To test this from a realm and have std.CurrentRealm/PreviousRealm work correctly, // this file is tested from gno.land/r/demo/tests/nestedpkg_test.gno // XXX: move test to ths directory once we support testing a package and -// specifying values for both PrevRealm and CurrentRealm. +// specifying values for both PreviousRealm and CurrentRealm. import ( "std" @@ -16,7 +16,7 @@ import ( func IsCallerSubPath() bool { var ( cur = std.CurrentRealm().PkgPath() + "/" - prev = std.PrevRealm().PkgPath() + "/" + prev = std.PreviousRealm().PkgPath() + "/" ) return strings.HasPrefix(prev, cur) } @@ -25,7 +25,7 @@ func IsCallerSubPath() bool { func AssertCallerIsSubPath() { var ( cur = std.CurrentRealm().PkgPath() + "/" - prev = std.PrevRealm().PkgPath() + "/" + prev = std.PreviousRealm().PkgPath() + "/" ) if !strings.HasPrefix(prev, cur) { panic("call restricted to nested packages. current realm is " + cur + ", previous realm is " + prev) @@ -36,7 +36,7 @@ func AssertCallerIsSubPath() { func IsCallerParentPath() bool { var ( cur = std.CurrentRealm().PkgPath() + "/" - prev = std.PrevRealm().PkgPath() + "/" + prev = std.PreviousRealm().PkgPath() + "/" ) return strings.HasPrefix(cur, prev) } @@ -45,7 +45,7 @@ func IsCallerParentPath() bool { func AssertCallerIsParentPath() { var ( cur = std.CurrentRealm().PkgPath() + "/" - prev = std.PrevRealm().PkgPath() + "/" + prev = std.PreviousRealm().PkgPath() + "/" ) if !strings.HasPrefix(cur, prev) { panic("call restricted to parent packages. current realm is " + cur + ", previous realm is " + prev) @@ -56,7 +56,7 @@ func AssertCallerIsParentPath() { func IsSameNamespace() bool { var ( cur = nsFromPath(std.CurrentRealm().PkgPath()) + "/" - prev = nsFromPath(std.PrevRealm().PkgPath()) + "/" + prev = nsFromPath(std.PreviousRealm().PkgPath()) + "/" ) return cur == prev } @@ -65,7 +65,7 @@ func IsSameNamespace() bool { func AssertIsSameNamespace() { var ( cur = nsFromPath(std.CurrentRealm().PkgPath()) + "/" - prev = nsFromPath(std.PrevRealm().PkgPath()) + "/" + prev = nsFromPath(std.PreviousRealm().PkgPath()) + "/" ) if cur != prev { panic("call restricted to packages from the same namespace. current realm is " + cur + ", previous realm is " + prev) diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno index 95bd2ac4959..ef2428ab2ed 100644 --- a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable.gno @@ -72,7 +72,7 @@ func (a *Authorizable) DeleteFromAuthList(addr std.Address) error { } func (a Authorizable) CallerOnAuthList() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if !a.authorized.Has(caller.String()) { return ErrNotInAuthList @@ -82,7 +82,7 @@ func (a Authorizable) CallerOnAuthList() error { } func (a Authorizable) AssertOnAuthList() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if !a.authorized.Has(caller.String()) { panic(ErrNotInAuthList) diff --git a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno index 10a5e411bdb..8fb1b9422f6 100644 --- a/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno +++ b/examples/gno.land/p/demo/ownable/exts/authorizable/authorizable_test.gno @@ -16,7 +16,7 @@ var ( func TestNewAuthorizable(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) // TODO(bug, issue #2371): should not be needed + std.TestSetOriginCaller(alice) // TODO(bug, issue #2371): should not be needed a := NewAuthorizable() got := a.Owner() @@ -39,7 +39,7 @@ func TestNewAuthorizableWithAddress(t *testing.T) { func TestCallerOnAuthList(t *testing.T) { a := NewAuthorizableWithAddress(alice) std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) if err := a.CallerOnAuthList(); err == ErrNotInAuthList { t.Fatalf("expected alice to be on the list") @@ -49,7 +49,7 @@ func TestCallerOnAuthList(t *testing.T) { func TestNotCallerOnAuthList(t *testing.T) { a := NewAuthorizableWithAddress(alice) std.TestSetRealm(std.NewUserRealm(bob)) - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) if err := a.CallerOnAuthList(); err == nil { t.Fatalf("expected bob to not be on the list") @@ -59,14 +59,14 @@ func TestNotCallerOnAuthList(t *testing.T) { func TestAddToAuthList(t *testing.T) { a := NewAuthorizableWithAddress(alice) std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) if err := a.AddToAuthList(bob); err != nil { t.Fatalf("Expected no error, got %v", err) } std.TestSetRealm(std.NewUserRealm(bob)) - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) if err := a.AddToAuthList(bob); err == nil { t.Fatalf("Expected AddToAuth to error while bob called it, but it didn't") @@ -76,7 +76,7 @@ func TestAddToAuthList(t *testing.T) { func TestDeleteFromList(t *testing.T) { a := NewAuthorizableWithAddress(alice) std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) if err := a.AddToAuthList(bob); err != nil { t.Fatalf("Expected no error, got %v", err) @@ -87,7 +87,7 @@ func TestDeleteFromList(t *testing.T) { } std.TestSetRealm(std.NewUserRealm(bob)) - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) // Try an unauthorized deletion if err := a.DeleteFromAuthList(alice); err == nil { @@ -95,7 +95,7 @@ func TestDeleteFromList(t *testing.T) { } std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) if err := a.DeleteFromAuthList(charlie); err != nil { t.Fatalf("Expected no error, got %v", err) @@ -104,11 +104,11 @@ func TestDeleteFromList(t *testing.T) { func TestAssertOnList(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) + std.TestSetOriginCaller(alice) a := NewAuthorizableWithAddress(alice) std.TestSetRealm(std.NewUserRealm(bob)) - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) uassert.PanicsWithMessage(t, ErrNotInAuthList.Error(), func() { a.AssertOnAuthList() diff --git a/examples/gno.land/p/demo/ownable/ownable.gno b/examples/gno.land/p/demo/ownable/ownable.gno index f565e27c0f2..96ed0955240 100644 --- a/examples/gno.land/p/demo/ownable/ownable.gno +++ b/examples/gno.land/p/demo/ownable/ownable.gno @@ -13,7 +13,7 @@ type Ownable struct { func New() *Ownable { return &Ownable{ - owner: std.PrevRealm().Addr(), + owner: std.PreviousRealm().Address(), } } @@ -65,18 +65,28 @@ func (o *Ownable) DropOwnership() error { } // Owner returns the owner address from Ownable -func (o Ownable) Owner() std.Address { +func (o *Ownable) Owner() std.Address { + if o == nil { + return std.Address("") + } return o.owner } // CallerIsOwner checks if the caller of the function is the Realm's owner -func (o Ownable) CallerIsOwner() bool { - return std.PrevRealm().Addr() == o.owner +func (o *Ownable) CallerIsOwner() bool { + if o == nil { + return false + } + return std.PreviousRealm().Address() == o.owner } // AssertCallerIsOwner panics if the caller is not the owner -func (o Ownable) AssertCallerIsOwner() { - if std.PrevRealm().Addr() != o.owner { +func (o *Ownable) AssertCallerIsOwner() { + if o == nil { + panic(ErrUnauthorized) + } + caller := std.PreviousRealm().Address() + if caller != o.owner { panic(ErrUnauthorized) } } diff --git a/examples/gno.land/p/demo/ownable/ownable_test.gno b/examples/gno.land/p/demo/ownable/ownable_test.gno index f58af9642c6..c2a9595e192 100644 --- a/examples/gno.land/p/demo/ownable/ownable_test.gno +++ b/examples/gno.land/p/demo/ownable/ownable_test.gno @@ -16,7 +16,7 @@ var ( func TestNew(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) // TODO(bug): should not be needed + std.TestSetOriginCaller(alice) // TODO(bug): should not be needed o := New() got := o.Owner() @@ -50,7 +50,7 @@ func TestCallerIsOwner(t *testing.T) { unauthorizedCaller := bob std.TestSetRealm(std.NewUserRealm(unauthorizedCaller)) - std.TestSetOrigCaller(unauthorizedCaller) // TODO(bug): should not be needed + std.TestSetOriginCaller(unauthorizedCaller) // TODO(bug): should not be needed uassert.False(t, o.CallerIsOwner()) } @@ -71,12 +71,12 @@ func TestDropOwnership(t *testing.T) { func TestErrUnauthorized(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) // TODO(bug): should not be needed + std.TestSetOriginCaller(alice) // TODO(bug): should not be needed o := New() std.TestSetRealm(std.NewUserRealm(bob)) - std.TestSetOrigCaller(bob) // TODO(bug): should not be needed + std.TestSetOriginCaller(bob) // TODO(bug): should not be needed uassert.ErrorContains(t, o.TransferOwnership(alice), ErrUnauthorized.Error()) uassert.ErrorContains(t, o.DropOwnership(), ErrUnauthorized.Error()) @@ -93,3 +93,51 @@ func TestErrInvalidAddress(t *testing.T) { err = o.TransferOwnership("10000000001000000000100000000010000000001000000000") uassert.ErrorContains(t, err, ErrInvalidAddress.Error()) } + +func TestAssertCallerIsOwner(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOriginCaller(alice) + + o := New() + + // Should not panic when caller is owner + o.AssertCallerIsOwner() + + // Should panic when caller is not owner + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOriginCaller(bob) + + defer func() { + r := recover() + if r == nil { + t.Error("expected panic but got none") + } + if r != ErrUnauthorized { + t.Errorf("expected ErrUnauthorized but got %v", r) + } + }() + o.AssertCallerIsOwner() +} + +func TestNilReceiver(t *testing.T) { + var o *Ownable + + owner := o.Owner() + if owner != std.Address("") { + t.Errorf("expected empty address but got %v", owner) + } + + isOwner := o.CallerIsOwner() + uassert.False(t, isOwner) + + defer func() { + r := recover() + if r == nil { + t.Error("expected panic but got none") + } + if r != ErrUnauthorized { + t.Errorf("expected ErrUnauthorized but got %v", r) + } + }() + o.AssertCallerIsOwner() +} diff --git a/examples/gno.land/p/demo/pausable/pausable.gno b/examples/gno.land/p/demo/pausable/pausable.gno index e6a85771fa6..b6059300409 100644 --- a/examples/gno.land/p/demo/pausable/pausable.gno +++ b/examples/gno.land/p/demo/pausable/pausable.gno @@ -1,29 +1,27 @@ +// Package pausable provides a mechanism to programmatically pause and unpause +// functionality. This package allows an owner, defined via an Ownable object, +// to restrict operations or methods when the contract is in a "paused" state. package pausable import ( + "errors" "std" "gno.land/p/demo/ownable" ) type Pausable struct { - *ownable.Ownable + o *ownable.Ownable paused bool } -// New returns a new Pausable struct with non-paused state as default -func New() *Pausable { - return &Pausable{ - Ownable: ownable.New(), - paused: false, - } -} +var ErrPaused = errors.New("pausable: realm is currently paused") // NewFromOwnable is the same as New, but with a pre-existing top-level ownable func NewFromOwnable(ownable *ownable.Ownable) *Pausable { return &Pausable{ - Ownable: ownable, - paused: false, + o: ownable, + paused: false, } } @@ -34,24 +32,29 @@ func (p Pausable) IsPaused() bool { // Pause sets the state of Pausable to true, meaning all pausable functions are paused func (p *Pausable) Pause() error { - if !p.CallerIsOwner() { + if !p.o.CallerIsOwner() { return ownable.ErrUnauthorized } p.paused = true - std.Emit("Paused", "account", p.Owner().String()) + std.Emit("Paused", "by", p.o.Owner().String()) return nil } // Unpause sets the state of Pausable to false, meaning all pausable functions are resumed func (p *Pausable) Unpause() error { - if !p.CallerIsOwner() { + if !p.o.CallerIsOwner() { return ownable.ErrUnauthorized } p.paused = false - std.Emit("Unpaused", "account", p.Owner().String()) + std.Emit("Unpaused", "by", p.o.Owner().String()) return nil } + +// Ownable returns the underlying ownable +func (p *Pausable) Ownable() *ownable.Ownable { + return p.o +} diff --git a/examples/gno.land/p/demo/pausable/pausable_test.gno b/examples/gno.land/p/demo/pausable/pausable_test.gno index c9557245bdf..41e4534ee5b 100644 --- a/examples/gno.land/p/demo/pausable/pausable_test.gno +++ b/examples/gno.land/p/demo/pausable/pausable_test.gno @@ -5,57 +5,49 @@ import ( "testing" "gno.land/p/demo/ownable" + "gno.land/p/demo/uassert" "gno.land/p/demo/urequire" ) var ( - firstCaller = std.Address("g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de") - secondCaller = std.Address("g127jydsh6cms3lrtdenydxsckh23a8d6emqcvfa") + firstCaller = std.Address("g1l9aypkr8xfvs82zeux486ddzec88ty69lue9de") + o = ownable.NewWithAddress(firstCaller) ) -func TestNew(t *testing.T) { - std.TestSetOrigCaller(firstCaller) - - result := New() - - urequire.False(t, result.paused, "Expected result to be unpaused") - urequire.Equal(t, firstCaller.String(), result.Owner().String()) -} - func TestNewFromOwnable(t *testing.T) { - std.TestSetOrigCaller(firstCaller) - o := ownable.New() + std.TestSetOriginCaller(firstCaller) - std.TestSetOrigCaller(secondCaller) result := NewFromOwnable(o) - - urequire.Equal(t, firstCaller.String(), result.Owner().String()) + urequire.Equal(t, firstCaller.String(), result.Ownable().Owner().String()) } func TestSetUnpaused(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetOriginCaller(firstCaller) + result := NewFromOwnable(o) - result := New() result.Unpause() - - urequire.False(t, result.IsPaused(), "Expected result to be unpaused") + uassert.False(t, result.IsPaused(), "Expected result to be unpaused") } func TestSetPaused(t *testing.T) { - std.TestSetOrigCaller(firstCaller) + std.TestSetOriginCaller(firstCaller) + result := NewFromOwnable(o) - result := New() result.Pause() - - urequire.True(t, result.IsPaused(), "Expected result to be paused") + uassert.True(t, result.IsPaused(), "Expected result to be paused") } func TestIsPaused(t *testing.T) { - std.TestSetOrigCaller(firstCaller) - - result := New() + result := NewFromOwnable(o) urequire.False(t, result.IsPaused(), "Expected result to be unpaused") + std.TestSetOriginCaller(firstCaller) result.Pause() - urequire.True(t, result.IsPaused(), "Expected result to be paused") + uassert.True(t, result.IsPaused(), "Expected result to be paused") +} + +func TestOwnable(t *testing.T) { + result := NewFromOwnable(o) + + uassert.Equal(t, result.Ownable().Owner(), o.Owner()) } diff --git a/examples/gno.land/p/demo/releases/changelog.gno b/examples/gno.land/p/demo/releases/changelog.gno index a17df3e3520..53436928da6 100644 --- a/examples/gno.land/p/demo/releases/changelog.gno +++ b/examples/gno.land/p/demo/releases/changelog.gno @@ -1,5 +1,11 @@ package releases +import ( + "gno.land/p/demo/ufmt" + "gno.land/p/moul/md" + "gno.land/p/moul/mdtable" +) + type changelog struct { name string releases []release @@ -31,6 +37,32 @@ func (c *changelog) NewRelease(version, url, notes string) { c.releases = append(c.releases, release) } +func (c changelog) RenderAsTable(max int) string { + out := md.H1(c.name) + + if len(c.releases) == 0 { + out += "No releases." + return out + } + + out += ufmt.Sprintf("See the %s changelog below.\n\n", c.name) + table := mdtable.Table{ + Headers: []string{"Version", "Link", "Notes"}, + } + + if max > len(c.releases) { + max = len(c.releases) + } + + for i := len(c.releases) - 1; i >= len(c.releases)-max; i-- { + r := c.releases[i] + table.Append([]string{r.version, r.Link(), r.notes}) + } + + out += table.String() + return out +} + func (c *changelog) Render(path string) string { if path == "" { output := "# " + c.name + "\n\n" diff --git a/examples/gno.land/p/demo/simpledao/dao.gno b/examples/gno.land/p/demo/simpledao/dao.gno index 837f64a41d6..a8194eecdea 100644 --- a/examples/gno.land/p/demo/simpledao/dao.gno +++ b/examples/gno.land/p/demo/simpledao/dao.gno @@ -56,7 +56,7 @@ func (s *SimpleDAO) Propose(request dao.ProposalRequest) (uint64, error) { var ( caller = getDAOCaller() - sentCoins = std.GetOrigSend() // Get the sent coins, if any + sentCoins = std.OriginSend() // Get the sent coins, if any canCoverFee = sentCoins.AmountOf("ugnot") >= minProposalFee.Amount ) @@ -168,7 +168,7 @@ func (s *SimpleDAO) VoteOnProposal(id uint64, option dao.VoteOption) error { func (s *SimpleDAO) ExecuteProposal(id uint64) error { var ( caller = getDAOCaller() - sentCoins = std.GetOrigSend() // Get the sent coins, if any + sentCoins = std.OriginSend() // Get the sent coins, if any canCoverFee = sentCoins.AmountOf("ugnot") >= minExecuteFee.Amount ) @@ -219,5 +219,5 @@ func (s *SimpleDAO) ExecuteProposal(id uint64) error { // However, the current MsgRun context does not persist escaping the main() scope. // Until a better solution is developed, this enables proposals to be made through a package deployment + init() func getDAOCaller() std.Address { - return std.GetOrigCaller() + return std.OriginCaller() } diff --git a/examples/gno.land/p/demo/simpledao/dao_test.gno b/examples/gno.land/p/demo/simpledao/dao_test.gno index 46251e24dad..1e67c0a8b64 100644 --- a/examples/gno.land/p/demo/simpledao/dao_test.gno +++ b/examples/gno.land/p/demo/simpledao/dao_test.gno @@ -74,7 +74,7 @@ func TestSimpleDAO_Propose(t *testing.T) { s = New(ms) ) - std.TestSetOrigSend(sentCoins, std.Coins{}) + std.TestSetOriginSend(sentCoins, std.Coins{}) _, err := s.Propose(dao.ProposalRequest{ Executor: ex, @@ -121,7 +121,7 @@ func TestSimpleDAO_Propose(t *testing.T) { // Set the sent coins to be lower // than the proposal fee - std.TestSetOrigSend(sentCoins, std.Coins{}) + std.TestSetOriginSend(sentCoins, std.Coins{}) _, err := s.Propose(dao.ProposalRequest{ Executor: ex, @@ -171,8 +171,8 @@ func TestSimpleDAO_Propose(t *testing.T) { // Set the sent coins to be enough // to cover the fee - std.TestSetOrigSend(sentCoins, std.Coins{}) - std.TestSetOrigCaller(proposer) + std.TestSetOriginSend(sentCoins, std.Coins{}) + std.TestSetOriginCaller(proposer) // Make sure the proposal was added id, err := s.Propose(dao.ProposalRequest{ @@ -221,7 +221,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { s = New(ms) ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Attempt to vote on the proposal uassert.ErrorContains( @@ -251,7 +251,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { s = New(ms) ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Attempt to vote on the proposal uassert.ErrorContains( @@ -285,7 +285,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Add an initial proposal id, err := s.addProposal(prop) @@ -327,7 +327,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Cast the initial vote urequire.NoError(t, prop.tally.castVote(member, dao.YesVote)) @@ -386,7 +386,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { majorityIndex := (len(members)*2)/3 + 1 // 2/3+ for _, m := range members[:majorityIndex] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -441,7 +441,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { majorityIndex := (len(members)*2)/3 + 1 // 2/3+ for _, m := range members[:majorityIndex] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -496,7 +496,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { majorityIndex := (len(members)*2)/3 + 1 // 2/3+ for _, m := range members[:majorityIndex] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -551,7 +551,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { // The first half votes yes for _, m := range members[:len(members)/2] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -562,7 +562,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { // The other half votes no for _, m := range members[len(members)/2:] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -618,7 +618,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { // The first quarter votes yes for _, m := range members[:len(members)/4] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -629,7 +629,7 @@ func TestSimpleDAO_VoteOnProposal(t *testing.T) { // The second quarter votes no for _, m := range members[len(members)/4 : len(members)/2] { - std.TestSetOrigCaller(m.Address) + std.TestSetOriginCaller(m.Address) // Attempt to vote on the proposal urequire.NoError( @@ -668,7 +668,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { // Set the sent coins to be lower // than the execute fee - std.TestSetOrigSend(sentCoins, std.Coins{}) + std.TestSetOriginSend(sentCoins, std.Coins{}) uassert.ErrorIs( t, @@ -699,7 +699,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { // Set the sent coins to be enough // so the execution can take place - std.TestSetOrigSend(sentCoins, std.Coins{}) + std.TestSetOriginSend(sentCoins, std.Coins{}) uassert.ErrorContains( t, @@ -726,7 +726,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Add an initial proposal id, err := s.addProposal(prop) @@ -752,7 +752,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { dao.ExecutionSuccessful, }, { - "execution failed", + "execution not succeeded", dao.ExecutionFailed, }, } @@ -776,7 +776,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Add an initial proposal id, err := s.addProposal(prop) @@ -820,7 +820,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Add an initial proposal id, err := s.addProposal(prop) @@ -864,7 +864,7 @@ func TestSimpleDAO_ExecuteProposal(t *testing.T) { } ) - std.TestSetOrigCaller(voter) + std.TestSetOriginCaller(voter) // Add an initial proposal id, err := s.addProposal(prop) diff --git a/examples/gno.land/p/demo/simpledao/propstore.gno b/examples/gno.land/p/demo/simpledao/propstore.gno index 91f2a883047..0a17fd84c15 100644 --- a/examples/gno.land/p/demo/simpledao/propstore.gno +++ b/examples/gno.land/p/demo/simpledao/propstore.gno @@ -144,7 +144,7 @@ func (s *SimpleDAO) Proposals(offset, count uint64) []dao.Proposal { s.proposals.Iterate( getProposalID(startIndex), getProposalID(endIndex), - func(_ string, val interface{}) bool { + func(_ string, val any) bool { prop := val.(*proposal) // Save the proposal diff --git a/examples/gno.land/p/demo/stack/stack.gno b/examples/gno.land/p/demo/stack/stack.gno index 1b8841f0a50..45a7e2ba2d5 100644 --- a/examples/gno.land/p/demo/stack/stack.gno +++ b/examples/gno.land/p/demo/stack/stack.gno @@ -6,7 +6,7 @@ type Stack struct { } type node struct { - value interface{} + value any prev *node } @@ -18,14 +18,14 @@ func (s *Stack) Len() int { return s.length } -func (s *Stack) Top() interface{} { +func (s *Stack) Top() any { if s.length == 0 { return nil } return s.top.value } -func (s *Stack) Pop() interface{} { +func (s *Stack) Pop() any { if s.length == 0 { return nil } @@ -36,7 +36,7 @@ func (s *Stack) Pop() interface{} { return node.value } -func (s *Stack) Push(value interface{}) { +func (s *Stack) Push(value any) { node := &node{value, s.top} s.top = node s.length += 1 diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno index be661e70129..cbd7fde04a4 100644 --- a/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime.gno @@ -26,7 +26,7 @@ func NewLifetimeSubscription(amount int64) *LifetimeSubscription { // processSubscription handles the subscription process for a given receiver. func (ls *LifetimeSubscription) processSubscription(receiver std.Address) error { - amount := std.GetOrigSend() + amount := std.OriginSend() if amount.AmountOf("ugnot") != ls.amount { return ErrAmt @@ -45,7 +45,7 @@ func (ls *LifetimeSubscription) processSubscription(receiver std.Address) error // Subscribe processes the payment for a lifetime subscription. func (ls *LifetimeSubscription) Subscribe() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() return ls.processSubscription(caller) } diff --git a/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno b/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno index efbae90c11c..eba1f2b5367 100644 --- a/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno +++ b/examples/gno.land/p/demo/subscription/lifetime/lifetime_test.gno @@ -18,11 +18,11 @@ func TestLifetimeSubscription(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) ls := NewLifetimeSubscription(1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := ls.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed") - err = ls.HasValidSubscription(std.PrevRealm().Addr()) + err = ls.HasValidSubscription(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected Alice to have access") } @@ -30,7 +30,7 @@ func TestLifetimeSubscriptionGift(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) ls := NewLifetimeSubscription(1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := ls.GiftSubscription(bob) uassert.NoError(t, err, "Expected ProcessPaymentGift to succeed for Bob") @@ -48,7 +48,7 @@ func TestUpdateAmountAuthorization(t *testing.T) { err := ls.UpdateAmount(2000) uassert.NoError(t, err, "Expected Alice to succeed in updating amount") - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) err = ls.UpdateAmount(3000) uassert.Error(t, err, "Expected Bob to fail when updating amount") @@ -58,7 +58,7 @@ func TestIncorrectPaymentAmount(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) ls := NewLifetimeSubscription(1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) err := ls.Subscribe() uassert.Error(t, err, "Expected payment to fail with incorrect amount") } @@ -67,11 +67,11 @@ func TestMultipleSubscriptionAttempts(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) ls := NewLifetimeSubscription(1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := ls.Subscribe() uassert.NoError(t, err, "Expected first subscription to succeed") - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err = ls.Subscribe() uassert.Error(t, err, "Expected second subscription to fail as Alice is already subscribed") } @@ -80,7 +80,7 @@ func TestGiftSubscriptionWithIncorrectAmount(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) ls := NewLifetimeSubscription(1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) err := ls.GiftSubscription(bob) uassert.Error(t, err, "Expected gift subscription to fail with incorrect amount") @@ -95,11 +95,11 @@ func TestUpdateAmountEffectiveness(t *testing.T) { err := ls.UpdateAmount(2000) uassert.NoError(t, err, "Expected Alice to succeed in updating amount") - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err = ls.Subscribe() uassert.Error(t, err, "Expected subscription to fail with old amount after update") - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 2000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 2000}}, nil) err = ls.Subscribe() uassert.NoError(t, err, "Expected subscription to succeed with new amount") } diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring.gno b/examples/gno.land/p/demo/subscription/recurring/recurring.gno index 8f116009aa6..2582c959bc6 100644 --- a/examples/gno.land/p/demo/subscription/recurring/recurring.gno +++ b/examples/gno.land/p/demo/subscription/recurring/recurring.gno @@ -43,7 +43,7 @@ func (rs *RecurringSubscription) HasValidSubscription(addr std.Address) error { // processSubscription processes the payment for a given receiver and renews or adds their subscription. func (rs *RecurringSubscription) processSubscription(receiver std.Address) error { - amount := std.GetOrigSend() + amount := std.OriginSend() if amount.AmountOf("ugnot") != rs.amount { return ErrAmt @@ -68,7 +68,7 @@ func (rs *RecurringSubscription) processSubscription(receiver std.Address) error // Subscribe handles the payment for the caller's subscription. func (rs *RecurringSubscription) Subscribe() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() return rs.processSubscription(caller) } diff --git a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno index e8bca15c0bf..c3289c05388 100644 --- a/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno +++ b/examples/gno.land/p/demo/subscription/recurring/recurring_test.gno @@ -19,14 +19,14 @@ func TestRecurringSubscription(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour*24, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := rs.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PrevRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected Alice to have access") - expiration, err := rs.GetExpiration(std.PrevRealm().Addr()) + _, err = rs.GetExpiration(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected to get expiration for Alice") } @@ -34,7 +34,7 @@ func TestRecurringSubscriptionGift(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour*24, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := rs.GiftSubscription(bob) uassert.NoError(t, err, "Expected ProcessPaymentGift to succeed for Bob") @@ -49,17 +49,17 @@ func TestRecurringSubscriptionExpiration(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := rs.Subscribe() uassert.NoError(t, err, "Expected ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PrevRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected Alice to have access") expiration := time.Now().Add(-time.Hour * 2) - rs.subs.Set(std.PrevRealm().Addr().String(), expiration) + rs.subs.Set(std.PreviousRealm().Address().String(), expiration) - err = rs.HasValidSubscription(std.PrevRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Address()) uassert.Error(t, err, "Expected Alice's subscription to be expired") } @@ -70,7 +70,7 @@ func TestUpdateAmountAuthorization(t *testing.T) { err := rs.UpdateAmount(2000) uassert.NoError(t, err, "Expected Alice to succeed in updating amount") - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) err = rs.UpdateAmount(3000) uassert.Error(t, err, "Expected Bob to fail when updating amount") } @@ -93,7 +93,7 @@ func TestIncorrectPaymentAmount(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour*24, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 500}}, nil) err := rs.Subscribe() uassert.Error(t, err, "Expected payment with incorrect amount to fail") } @@ -102,11 +102,11 @@ func TestMultiplePaymentsForSameUser(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour*24, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := rs.Subscribe() uassert.NoError(t, err, "Expected first ProcessPayment to succeed for Alice") - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err = rs.Subscribe() uassert.Error(t, err, "Expected second ProcessPayment to fail for Alice due to existing subscription") } @@ -115,20 +115,20 @@ func TestRecurringSubscriptionWithMultiplePayments(t *testing.T) { std.TestSetRealm(std.NewUserRealm(alice)) rs := NewRecurringSubscription(time.Hour, 1000) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err := rs.Subscribe() uassert.NoError(t, err, "Expected first ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PrevRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected Alice to have access after first payment") expiration := time.Now().Add(-time.Hour * 2) - rs.subs.Set(std.PrevRealm().Addr().String(), expiration) + rs.subs.Set(std.PreviousRealm().Address().String(), expiration) - std.TestSetOrigSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) + std.TestSetOriginSend([]std.Coin{{Denom: "ugnot", Amount: 1000}}, nil) err = rs.Subscribe() uassert.NoError(t, err, "Expected second ProcessPayment to succeed for Alice") - err = rs.HasValidSubscription(std.PrevRealm().Addr()) + err = rs.HasValidSubscription(std.PreviousRealm().Address()) uassert.NoError(t, err, "Expected Alice to have access after second payment") } diff --git a/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno b/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno index 17d6c466ed5..2aebd9d4aae 100644 --- a/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno +++ b/examples/gno.land/p/demo/tamagotchi/z0_filetest.gno @@ -1,10 +1,9 @@ package main import ( + "os_test" "time" - "internal/os_test" - "gno.land/p/demo/tamagotchi" ) diff --git a/examples/gno.land/p/demo/tests/subtests/subtests.gno b/examples/gno.land/p/demo/tests/subtests/subtests.gno index e8796a73081..8a7f3572cfa 100644 --- a/examples/gno.land/p/demo/tests/subtests/subtests.gno +++ b/examples/gno.land/p/demo/tests/subtests/subtests.gno @@ -8,8 +8,8 @@ func GetCurrentRealm() std.Realm { return std.CurrentRealm() } -func GetPrevRealm() std.Realm { - return std.PrevRealm() +func GetPreviousRealm() std.Realm { + return std.PreviousRealm() } func Exec(fn func()) { diff --git a/examples/gno.land/p/demo/tests/tests.gno b/examples/gno.land/p/demo/tests/tests.gno index ffad5b8c8cd..ae3468c5d48 100644 --- a/examples/gno.land/p/demo/tests/tests.gno +++ b/examples/gno.land/p/demo/tests/tests.gno @@ -47,12 +47,12 @@ func ModifyTestRealmObject2c() { SomeValue3.Field = "modified" } -func GetPrevRealm() std.Realm { - return std.PrevRealm() +func GetPreviousRealm() std.Realm { + return std.PreviousRealm() } -func GetPSubtestsPrevRealm() std.Realm { - return psubtests.GetPrevRealm() +func GetPSubtestsPreviousRealm() std.Realm { + return psubtests.GetPreviousRealm() } // Warning: unsafe pattern. diff --git a/examples/gno.land/p/demo/testutils/misc.gno b/examples/gno.land/p/demo/testutils/misc.gno index d48304ad441..60dd611ef49 100644 --- a/examples/gno.land/p/demo/testutils/misc.gno +++ b/examples/gno.land/p/demo/testutils/misc.gno @@ -1,6 +1,6 @@ package testutils -// For testing std.GetCallerAt(). +// For testing std.CallerAt(). func WrapCall(fn func()) { fn() } diff --git a/examples/gno.land/p/demo/todolist/todolist.gno b/examples/gno.land/p/demo/todolist/todolist.gno index a675344655f..011825bb780 100644 --- a/examples/gno.land/p/demo/todolist/todolist.gno +++ b/examples/gno.land/p/demo/todolist/todolist.gno @@ -22,7 +22,7 @@ func NewTodoList(title string) *TodoList { return &TodoList{ Title: title, Tasks: avl.NewTree(), - Owner: std.GetOrigCaller(), + Owner: std.OriginCaller(), } } @@ -47,7 +47,7 @@ func (tl *TodoList) RemoveTask(taskId string) { func (tl *TodoList) GetTasks() []*Task { tasks := make([]*Task, 0, tl.Tasks.Size()) - tl.Tasks.Iterate("", "", func(key string, value interface{}) bool { + tl.Tasks.Iterate("", "", func(key string, value any) bool { tasks = append(tasks, value.(*Task)) return false }) diff --git a/examples/gno.land/p/demo/todolist/todolist_test.gno b/examples/gno.land/p/demo/todolist/todolist_test.gno index 85836e2a17f..26daf05a374 100644 --- a/examples/gno.land/p/demo/todolist/todolist_test.gno +++ b/examples/gno.land/p/demo/todolist/todolist_test.gno @@ -13,7 +13,7 @@ func TestNewTodoList(t *testing.T) { uassert.Equal(t, title, todoList.GetTodolistTitle()) uassert.Equal(t, 0, len(todoList.GetTasks())) - uassert.Equal(t, std.GetOrigCaller().String(), todoList.GetTodolistOwner().String()) + uassert.Equal(t, std.OriginCaller().String(), todoList.GetTodolistOwner().String()) } func TestNewTask(t *testing.T) { diff --git a/examples/gno.land/p/demo/uassert/helpers.gno b/examples/gno.land/p/demo/uassert/helpers.gno index 76657e75ed4..bcea4f3b38f 100644 --- a/examples/gno.land/p/demo/uassert/helpers.gno +++ b/examples/gno.land/p/demo/uassert/helpers.gno @@ -2,7 +2,7 @@ package uassert import "strings" -func fail(t TestingT, customMsgs []string, failureMessage string, args ...interface{}) bool { +func fail(t TestingT, customMsgs []string, failureMessage string, args ...any) bool { customMsg := "" if len(customMsgs) > 0 { customMsg = strings.Join(customMsgs, " ") @@ -14,7 +14,7 @@ func fail(t TestingT, customMsgs []string, failureMessage string, args ...interf return false } -func autofail(t TestingT, success bool, customMsgs []string, failureMessage string, args ...interface{}) bool { +func autofail(t TestingT, success bool, customMsgs []string, failureMessage string, args ...any) bool { if success { return true } diff --git a/examples/gno.land/p/demo/uassert/mock_test.gno b/examples/gno.land/p/demo/uassert/mock_test.gno index b611faab668..fe7259cb57e 100644 --- a/examples/gno.land/p/demo/uassert/mock_test.gno +++ b/examples/gno.land/p/demo/uassert/mock_test.gno @@ -7,25 +7,25 @@ import ( type mockTestingT struct { fmt string - args []interface{} + args []any } // --- interface mock var _ TestingT = (*mockTestingT)(nil) -func (mockT *mockTestingT) Helper() { /* noop */ } -func (mockT *mockTestingT) Skip(args ...interface{}) { /* not implmented */ } -func (mockT *mockTestingT) Fail() { /* not implmented */ } -func (mockT *mockTestingT) FailNow() { /* not implmented */ } -func (mockT *mockTestingT) Logf(fmt string, args ...interface{}) { /* noop */ } +func (mockT *mockTestingT) Helper() { /* noop */ } +func (mockT *mockTestingT) Skip(args ...any) { /* not implmented */ } +func (mockT *mockTestingT) Fail() { /* not implmented */ } +func (mockT *mockTestingT) FailNow() { /* not implmented */ } +func (mockT *mockTestingT) Logf(fmt string, args ...any) { /* noop */ } -func (mockT *mockTestingT) Fatalf(fmt string, args ...interface{}) { +func (mockT *mockTestingT) Fatalf(fmt string, args ...any) { mockT.fmt = "fatal: " + fmt mockT.args = args } -func (mockT *mockTestingT) Errorf(fmt string, args ...interface{}) { +func (mockT *mockTestingT) Errorf(fmt string, args ...any) { mockT.fmt = "error: " + fmt mockT.args = args } diff --git a/examples/gno.land/p/demo/uassert/types.gno b/examples/gno.land/p/demo/uassert/types.gno index 83950b5e8a4..cfdcc38f3a0 100644 --- a/examples/gno.land/p/demo/uassert/types.gno +++ b/examples/gno.land/p/demo/uassert/types.gno @@ -2,10 +2,10 @@ package uassert type TestingT interface { Helper() - Skip(args ...interface{}) - Fatalf(fmt string, args ...interface{}) - Errorf(fmt string, args ...interface{}) - Logf(fmt string, args ...interface{}) + Skip(args ...any) + Fatalf(fmt string, args ...any) + Errorf(fmt string, args ...any) + Logf(fmt string, args ...any) Fail() FailNow() } diff --git a/examples/gno.land/p/demo/uassert/uassert.gno b/examples/gno.land/p/demo/uassert/uassert.gno index f9c0ab3efc8..e256cdf0c6d 100644 --- a/examples/gno.land/p/demo/uassert/uassert.gno +++ b/examples/gno.land/p/demo/uassert/uassert.gno @@ -108,7 +108,7 @@ func NotPanics(t TestingT, f func(), msgs ...string) bool { } // Equal asserts that two objects are equal. -func Equal(t TestingT, expected, actual interface{}, msgs ...string) bool { +func Equal(t TestingT, expected, actual any, msgs ...string) bool { t.Helper() if expected == nil || actual == nil { @@ -246,7 +246,7 @@ func Equal(t TestingT, expected, actual interface{}, msgs ...string) bool { } // NotEqual asserts that two objects are not equal. -func NotEqual(t TestingT, expected, actual interface{}, msgs ...string) bool { +func NotEqual(t TestingT, expected, actual any, msgs ...string) bool { t.Helper() if expected == nil || actual == nil { @@ -379,7 +379,7 @@ func NotEqual(t TestingT, expected, actual interface{}, msgs ...string) bool { return true } -func isNumberEmpty(n interface{}) (isNumber, isEmpty bool) { +func isNumberEmpty(n any) (isNumber, isEmpty bool) { switch n := n.(type) { // NOTE: the cases are split individually, so that n becomes of the // asserted type; the type of '0' was correctly inferred and converted @@ -411,7 +411,7 @@ func isNumberEmpty(n interface{}) (isNumber, isEmpty bool) { } return false, false } -func Empty(t TestingT, obj interface{}, msgs ...string) bool { +func Empty(t TestingT, obj any, msgs ...string) bool { t.Helper() isNumber, isEmpty := isNumberEmpty(obj) @@ -437,7 +437,7 @@ func Empty(t TestingT, obj interface{}, msgs ...string) bool { return true } -func NotEmpty(t TestingT, obj interface{}, msgs ...string) bool { +func NotEmpty(t TestingT, obj any, msgs ...string) bool { t.Helper() isNumber, isEmpty := isNumberEmpty(obj) if isNumber { diff --git a/examples/gno.land/p/demo/uassert/uassert_test.gno b/examples/gno.land/p/demo/uassert/uassert_test.gno index 7862eca7305..84283bf504c 100644 --- a/examples/gno.land/p/demo/uassert/uassert_test.gno +++ b/examples/gno.land/p/demo/uassert/uassert_test.gno @@ -120,8 +120,8 @@ func TestEqual(t *testing.T) { mockT := new(mockTestingT) cases := []struct { - expected interface{} - actual interface{} + expected any + actual any result bool remark string }{ @@ -161,8 +161,8 @@ func TestNotEqual(t *testing.T) { mockT := new(mockTestingT) cases := []struct { - expected interface{} - actual interface{} + expected any + actual any result bool remark string }{ @@ -211,7 +211,7 @@ func TestEmpty(t *testing.T) { mockT := new(mockTestingT) cases := []struct { - obj interface{} + obj any expectedEmpty bool }{ // expected to be empty @@ -329,7 +329,7 @@ func TestNotEmpty(t *testing.T) { mockT := new(mockTestingT) cases := []struct { - obj interface{} + obj any expectedNotEmpty bool }{ // expected to be empty diff --git a/examples/gno.land/p/demo/ufmt/ufmt.gno b/examples/gno.land/p/demo/ufmt/ufmt.gno index c9acee1c910..2e15ead6bc1 100644 --- a/examples/gno.land/p/demo/ufmt/ufmt.gno +++ b/examples/gno.land/p/demo/ufmt/ufmt.gno @@ -12,7 +12,7 @@ import ( // Println formats using the default formats for its operands and writes to standard output. // Println writes the given arguments to standard output with spaces between arguments // and a newline at the end. -func Println(args ...interface{}) { +func Println(args ...any) { var strs []string for _, arg := range args { switch v := arg.(type) { @@ -73,7 +73,7 @@ func Println(args ...interface{}) { // %T: formats the type of the value. // %v: formats the value with a default representation appropriate for the value's type // %%: outputs a literal %. Does not consume an argument. -func Sprintf(format string, args ...interface{}) string { +func Sprintf(format string, args ...any) string { // we use runes to handle multi-byte characters sTor := []rune(format) end := len(sTor) @@ -306,7 +306,7 @@ func Sprintf(format string, args ...interface{}) string { // // fallback("s", 8) -> "%!s(int=8)" // fallback("d", nil) -> "%!d()", and so on. -func fallback(verb string, arg interface{}) string { +func fallback(verb string, arg any) string { var s string switch v := arg.(type) { case string: @@ -342,7 +342,7 @@ func fallback(verb string, arg interface{}) string { // Get the name of the type of `v` as a string. // The recognized type of v is currently limited to native non-composite types. // An error is returned otherwise. -func typeToString(v interface{}) (string, error) { +func typeToString(v any) (string, error) { switch v.(type) { case string: return "string", nil @@ -406,6 +406,6 @@ func (e *errMsg) Error() string { // Currently supports only uint, uint64, int, int64. // %t: formats a boolean value to "true" or "false". // %%: outputs a literal %. Does not consume an argument. -func Errorf(format string, args ...interface{}) error { +func Errorf(format string, args ...any) error { return &errMsg{Sprintf(format, args...)} } diff --git a/examples/gno.land/p/demo/ufmt/ufmt_test.gno b/examples/gno.land/p/demo/ufmt/ufmt_test.gno index 1a4d4e7e6f2..82aa3fea32e 100644 --- a/examples/gno.land/p/demo/ufmt/ufmt_test.gno +++ b/examples/gno.land/p/demo/ufmt/ufmt_test.gno @@ -16,86 +16,86 @@ func TestSprintf(t *testing.T) { tru := true cases := []struct { format string - values []interface{} + values []any expectedOutput string }{ - {"hello %s!", []interface{}{"planet"}, "hello planet!"}, - {"hello %v!", []interface{}{"planet"}, "hello planet!"}, - {"hi %%%s!", []interface{}{"worl%d"}, "hi %worl%d!"}, - {"%s %c %d %t", []interface{}{"foo", 'α', 421, true}, "foo α 421 true"}, - {"string [%s]", []interface{}{"foo"}, "string [foo]"}, - {"int [%d]", []interface{}{int(42)}, "int [42]"}, - {"int [%v]", []interface{}{int(42)}, "int [42]"}, - {"int8 [%d]", []interface{}{int8(8)}, "int8 [8]"}, - {"int8 [%v]", []interface{}{int8(8)}, "int8 [8]"}, - {"int16 [%d]", []interface{}{int16(16)}, "int16 [16]"}, - {"int16 [%v]", []interface{}{int16(16)}, "int16 [16]"}, - {"int32 [%d]", []interface{}{int32(32)}, "int32 [32]"}, - {"int32 [%v]", []interface{}{int32(32)}, "int32 [32]"}, - {"int64 [%d]", []interface{}{int64(64)}, "int64 [64]"}, - {"int64 [%v]", []interface{}{int64(64)}, "int64 [64]"}, - {"uint [%d]", []interface{}{uint(42)}, "uint [42]"}, - {"uint [%v]", []interface{}{uint(42)}, "uint [42]"}, - {"uint8 [%d]", []interface{}{uint8(8)}, "uint8 [8]"}, - {"uint8 [%v]", []interface{}{uint8(8)}, "uint8 [8]"}, - {"uint16 [%d]", []interface{}{uint16(16)}, "uint16 [16]"}, - {"uint16 [%v]", []interface{}{uint16(16)}, "uint16 [16]"}, - {"uint32 [%d]", []interface{}{uint32(32)}, "uint32 [32]"}, - {"uint32 [%v]", []interface{}{uint32(32)}, "uint32 [32]"}, - {"uint64 [%d]", []interface{}{uint64(64)}, "uint64 [64]"}, - {"uint64 [%v]", []interface{}{uint64(64)}, "uint64 [64]"}, - {"float64 [%e]", []interface{}{float64(64.1)}, "float64 [6.41e+01]"}, - {"float64 [%E]", []interface{}{float64(64.1)}, "float64 [6.41E+01]"}, - {"float64 [%f]", []interface{}{float64(64.1)}, "float64 [64.100000]"}, - {"float64 [%F]", []interface{}{float64(64.1)}, "float64 [64.100000]"}, - {"float64 [%g]", []interface{}{float64(64.1)}, "float64 [64.1]"}, - {"float64 [%G]", []interface{}{float64(64.1)}, "float64 [64.1]"}, - {"bool [%t]", []interface{}{true}, "bool [true]"}, - {"bool [%v]", []interface{}{true}, "bool [true]"}, - {"bool [%t]", []interface{}{false}, "bool [false]"}, - {"bool [%v]", []interface{}{false}, "bool [false]"}, + {"hello %s!", []any{"planet"}, "hello planet!"}, + {"hello %v!", []any{"planet"}, "hello planet!"}, + {"hi %%%s!", []any{"worl%d"}, "hi %worl%d!"}, + {"%s %c %d %t", []any{"foo", 'α', 421, true}, "foo α 421 true"}, + {"string [%s]", []any{"foo"}, "string [foo]"}, + {"int [%d]", []any{int(42)}, "int [42]"}, + {"int [%v]", []any{int(42)}, "int [42]"}, + {"int8 [%d]", []any{int8(8)}, "int8 [8]"}, + {"int8 [%v]", []any{int8(8)}, "int8 [8]"}, + {"int16 [%d]", []any{int16(16)}, "int16 [16]"}, + {"int16 [%v]", []any{int16(16)}, "int16 [16]"}, + {"int32 [%d]", []any{int32(32)}, "int32 [32]"}, + {"int32 [%v]", []any{int32(32)}, "int32 [32]"}, + {"int64 [%d]", []any{int64(64)}, "int64 [64]"}, + {"int64 [%v]", []any{int64(64)}, "int64 [64]"}, + {"uint [%d]", []any{uint(42)}, "uint [42]"}, + {"uint [%v]", []any{uint(42)}, "uint [42]"}, + {"uint8 [%d]", []any{uint8(8)}, "uint8 [8]"}, + {"uint8 [%v]", []any{uint8(8)}, "uint8 [8]"}, + {"uint16 [%d]", []any{uint16(16)}, "uint16 [16]"}, + {"uint16 [%v]", []any{uint16(16)}, "uint16 [16]"}, + {"uint32 [%d]", []any{uint32(32)}, "uint32 [32]"}, + {"uint32 [%v]", []any{uint32(32)}, "uint32 [32]"}, + {"uint64 [%d]", []any{uint64(64)}, "uint64 [64]"}, + {"uint64 [%v]", []any{uint64(64)}, "uint64 [64]"}, + {"float64 [%e]", []any{float64(64.1)}, "float64 [6.41e+01]"}, + {"float64 [%E]", []any{float64(64.1)}, "float64 [6.41E+01]"}, + {"float64 [%f]", []any{float64(64.1)}, "float64 [64.100000]"}, + {"float64 [%F]", []any{float64(64.1)}, "float64 [64.100000]"}, + {"float64 [%g]", []any{float64(64.1)}, "float64 [64.1]"}, + {"float64 [%G]", []any{float64(64.1)}, "float64 [64.1]"}, + {"bool [%t]", []any{true}, "bool [true]"}, + {"bool [%v]", []any{true}, "bool [true]"}, + {"bool [%t]", []any{false}, "bool [false]"}, + {"bool [%v]", []any{false}, "bool [false]"}, {"no args", nil, "no args"}, {"finish with %", nil, "finish with %"}, - {"stringer [%s]", []interface{}{stringer{}}, "stringer [I'm a stringer]"}, + {"stringer [%s]", []any{stringer{}}, "stringer [I'm a stringer]"}, {"â", nil, "â"}, {"Hello, World! 😊", nil, "Hello, World! 😊"}, - {"unicode formatting: %s", []interface{}{"😊"}, "unicode formatting: 😊"}, - {"invalid hex [%x]", []interface{}{"invalid"}, "invalid hex [(unhandled)]"}, - {"rune as character [%c]", []interface{}{rune('A')}, "rune as character [A]"}, - {"int as character [%c]", []interface{}{int('B')}, "int as character [B]"}, - {"quoted string [%q]", []interface{}{"hello"}, "quoted string [\"hello\"]"}, - {"quoted string with escape [%q]", []interface{}{"\thello\nworld\\"}, "quoted string with escape [\"\\thello\\nworld\\\\\"]"}, - {"invalid quoted string [%q]", []interface{}{123}, "invalid quoted string [(unhandled)]"}, - {"type of bool [%T]", []interface{}{true}, "type of bool [bool]"}, - {"type of int [%T]", []interface{}{123}, "type of int [int]"}, - {"type of string [%T]", []interface{}{"hello"}, "type of string [string]"}, - {"type of []byte [%T]", []interface{}{[]byte{1, 2, 3}}, "type of []byte [[]byte]"}, - {"type of []rune [%T]", []interface{}{[]rune{'a', 'b', 'c'}}, "type of []rune [[]rune]"}, - {"type of unknown [%T]", []interface{}{struct{}{}}, "type of unknown [unknown]"}, + {"unicode formatting: %s", []any{"😊"}, "unicode formatting: 😊"}, + {"invalid hex [%x]", []any{"invalid"}, "invalid hex [(unhandled)]"}, + {"rune as character [%c]", []any{rune('A')}, "rune as character [A]"}, + {"int as character [%c]", []any{int('B')}, "int as character [B]"}, + {"quoted string [%q]", []any{"hello"}, "quoted string [\"hello\"]"}, + {"quoted string with escape [%q]", []any{"\thello\nworld\\"}, "quoted string with escape [\"\\thello\\nworld\\\\\"]"}, + {"invalid quoted string [%q]", []any{123}, "invalid quoted string [(unhandled)]"}, + {"type of bool [%T]", []any{true}, "type of bool [bool]"}, + {"type of int [%T]", []any{123}, "type of int [int]"}, + {"type of string [%T]", []any{"hello"}, "type of string [string]"}, + {"type of []byte [%T]", []any{[]byte{1, 2, 3}}, "type of []byte [[]byte]"}, + {"type of []rune [%T]", []any{[]rune{'a', 'b', 'c'}}, "type of []rune [[]rune]"}, + {"type of unknown [%T]", []any{struct{}{}}, "type of unknown [unknown]"}, // mismatch printing - {"%s", []interface{}{nil}, "%!s()"}, - {"%s", []interface{}{421}, "%!s(int=421)"}, - {"%s", []interface{}{"z"}, "z"}, - {"%s", []interface{}{tru}, "%!s(bool=true)"}, - {"%s", []interface{}{'z'}, "%!s(int32=122)"}, + {"%s", []any{nil}, "%!s()"}, + {"%s", []any{421}, "%!s(int=421)"}, + {"%s", []any{"z"}, "z"}, + {"%s", []any{tru}, "%!s(bool=true)"}, + {"%s", []any{'z'}, "%!s(int32=122)"}, - {"%c", []interface{}{nil}, "%!c()"}, - {"%c", []interface{}{421}, "ƥ"}, - {"%c", []interface{}{"z"}, "%!c(string=z)"}, - {"%c", []interface{}{tru}, "%!c(bool=true)"}, - {"%c", []interface{}{'z'}, "z"}, + {"%c", []any{nil}, "%!c()"}, + {"%c", []any{421}, "ƥ"}, + {"%c", []any{"z"}, "%!c(string=z)"}, + {"%c", []any{tru}, "%!c(bool=true)"}, + {"%c", []any{'z'}, "z"}, - {"%d", []interface{}{nil}, "%!d()"}, - {"%d", []interface{}{421}, "421"}, - {"%d", []interface{}{"z"}, "%!d(string=z)"}, - {"%d", []interface{}{tru}, "%!d(bool=true)"}, - {"%d", []interface{}{'z'}, "122"}, + {"%d", []any{nil}, "%!d()"}, + {"%d", []any{421}, "421"}, + {"%d", []any{"z"}, "%!d(string=z)"}, + {"%d", []any{tru}, "%!d(bool=true)"}, + {"%d", []any{'z'}, "122"}, - {"%t", []interface{}{nil}, "%!t()"}, - {"%t", []interface{}{421}, "%!t(int=421)"}, - {"%t", []interface{}{"z"}, "%!t(string=z)"}, - {"%t", []interface{}{tru}, "true"}, - {"%t", []interface{}{'z'}, "%!t(int32=122)"}, + {"%t", []any{nil}, "%!t()"}, + {"%t", []any{421}, "%!t(int=421)"}, + {"%t", []any{"z"}, "%!t(string=z)"}, + {"%t", []any{tru}, "true"}, + {"%t", []any{'z'}, "%!t(int32=122)"}, } for _, tc := range cases { @@ -113,37 +113,37 @@ func TestErrorf(t *testing.T) { tests := []struct { name string format string - args []interface{} + args []any expected string }{ { name: "simple string", format: "error: %s", - args: []interface{}{"something went wrong"}, + args: []any{"something went wrong"}, expected: "error: something went wrong", }, { name: "integer value", format: "value: %d", - args: []interface{}{42}, + args: []any{42}, expected: "value: 42", }, { name: "boolean value", format: "success: %t", - args: []interface{}{true}, + args: []any{true}, expected: "success: true", }, { name: "multiple values", format: "error %d: %s (success=%t)", - args: []interface{}{123, "failure occurred", false}, + args: []any{123, "failure occurred", false}, expected: "error 123: failure occurred (success=false)", }, { name: "literal percent", format: "literal %%", - args: []interface{}{}, + args: []any{}, expected: "literal %", }, } @@ -171,32 +171,32 @@ func TestPrintErrors(t *testing.T) { func TestPrintln(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "Empty args", - args: []interface{}{}, + args: []any{}, expected: "", }, { name: "String args", - args: []interface{}{"Hello", "World"}, + args: []any{"Hello", "World"}, expected: "Hello World", }, { name: "Integer args", - args: []interface{}{1, 2, 3}, + args: []any{1, 2, 3}, expected: "1 2 3", }, { name: "Mixed args", - args: []interface{}{"Hello", 42, true, false, "World"}, + args: []any{"Hello", 42, true, false, "World"}, expected: "Hello 42 true false World", }, { name: "Unhandled type", - args: []interface{}{"Hello", 3.14, []int{1, 2, 3}}, + args: []any{"Hello", 3.14, []int{1, 2, 3}}, expected: "Hello (unhandled) (unhandled)", }, } diff --git a/examples/gno.land/p/demo/uint256/conversion.gno b/examples/gno.land/p/demo/uint256/conversion.gno index c2f228f314c..17244d96aa9 100644 --- a/examples/gno.land/p/demo/uint256/conversion.gno +++ b/examples/gno.land/p/demo/uint256/conversion.gno @@ -83,7 +83,7 @@ func (z *Uint) Dec() string { return string(out[pos-len(buf):]) } -func (z *Uint) Scan(src interface{}) error { +func (z *Uint) Scan(src any) error { if src == nil { z.Clear() return nil diff --git a/examples/gno.land/p/demo/uint256/conversion_test.gno b/examples/gno.land/p/demo/uint256/conversion_test.gno index 3942a102511..7607e26f232 100644 --- a/examples/gno.land/p/demo/uint256/conversion_test.gno +++ b/examples/gno.land/p/demo/uint256/conversion_test.gno @@ -60,7 +60,7 @@ func TestDec(t *testing.T) { func TestUint_Scan(t *testing.T) { tests := []struct { name string - input interface{} + input any want *Uint wantErr bool }{ diff --git a/examples/gno.land/p/demo/urequire/urequire.gno b/examples/gno.land/p/demo/urequire/urequire.gno index 94721dd85a7..5be1a62d9c1 100644 --- a/examples/gno.land/p/demo/urequire/urequire.gno +++ b/examples/gno.land/p/demo/urequire/urequire.gno @@ -70,7 +70,7 @@ func NotPanics(t uassert.TestingT, f func(), msgs ...string) { t.FailNow() } -func Equal(t uassert.TestingT, expected, actual interface{}, msgs ...string) { +func Equal(t uassert.TestingT, expected, actual any, msgs ...string) { t.Helper() if uassert.Equal(t, expected, actual, msgs...) { return @@ -78,7 +78,7 @@ func Equal(t uassert.TestingT, expected, actual interface{}, msgs ...string) { t.FailNow() } -func NotEqual(t uassert.TestingT, expected, actual interface{}, msgs ...string) { +func NotEqual(t uassert.TestingT, expected, actual any, msgs ...string) { t.Helper() if uassert.NotEqual(t, expected, actual, msgs...) { return @@ -86,7 +86,7 @@ func NotEqual(t uassert.TestingT, expected, actual interface{}, msgs ...string) t.FailNow() } -func Empty(t uassert.TestingT, obj interface{}, msgs ...string) { +func Empty(t uassert.TestingT, obj any, msgs ...string) { t.Helper() if uassert.Empty(t, obj, msgs...) { return @@ -94,7 +94,7 @@ func Empty(t uassert.TestingT, obj interface{}, msgs ...string) { t.FailNow() } -func NotEmpty(t uassert.TestingT, obj interface{}, msgs ...string) { +func NotEmpty(t uassert.TestingT, obj any, msgs ...string) { t.Helper() if uassert.NotEmpty(t, obj, msgs...) { return diff --git a/examples/gno.land/p/demo/users/gno.mod b/examples/gno.land/p/demo/users/gno.mod deleted file mode 100644 index ad652803fb8..00000000000 --- a/examples/gno.land/p/demo/users/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/users diff --git a/examples/gno.land/p/demo/users/types.gno b/examples/gno.land/p/demo/users/types.gno deleted file mode 100644 index d28b6a8ee42..00000000000 --- a/examples/gno.land/p/demo/users/types.gno +++ /dev/null @@ -1,14 +0,0 @@ -package users - -type AddressOrName string - -func (aon AddressOrName) IsName() bool { - return aon != "" && aon[0] == '@' -} - -func (aon AddressOrName) GetName() (string, bool) { - if len(aon) >= 2 && aon[0] == '@' { - return string(aon[1:]), true - } - return "", false -} diff --git a/examples/gno.land/p/demo/users/users.gno b/examples/gno.land/p/demo/users/users.gno deleted file mode 100644 index 204eaf19918..00000000000 --- a/examples/gno.land/p/demo/users/users.gno +++ /dev/null @@ -1,31 +0,0 @@ -package users - -import ( - "std" - "strconv" -) - -//---------------------------------------- -// Types - -type User struct { - Address std.Address - Name string - Profile string - Number int - Invites int - Inviter std.Address -} - -func (u *User) Render() string { - str := "## user " + u.Name + "\n" + - "\n" + - " * address = " + string(u.Address) + "\n" + - " * " + strconv.Itoa(u.Invites) + " invites\n" - if u.Inviter != "" { - str = str + " * invited by " + string(u.Inviter) + "\n" - } - str = str + "\n" + - u.Profile + "\n" - return str -} diff --git a/examples/gno.land/p/demo/users/users_test.gno b/examples/gno.land/p/demo/users/users_test.gno deleted file mode 100644 index 82abcb9fccb..00000000000 --- a/examples/gno.land/p/demo/users/users_test.gno +++ /dev/null @@ -1 +0,0 @@ -package users diff --git a/examples/gno.land/p/jeronimoalbi/datasource/datasource.gno b/examples/gno.land/p/jeronimoalbi/datasource/datasource.gno index bf80964a9a0..07dbb1a78fe 100644 --- a/examples/gno.land/p/jeronimoalbi/datasource/datasource.gno +++ b/examples/gno.land/p/jeronimoalbi/datasource/datasource.gno @@ -20,7 +20,7 @@ type ( Has(name string) bool // Get retrieves the value associated with the given field. - Get(name string) (value interface{}, found bool) + Get(name string) (value any, found bool) } // Record defines a datasource record. diff --git a/examples/gno.land/p/jeronimoalbi/datasource/query.gno b/examples/gno.land/p/jeronimoalbi/datasource/query.gno index f971f9c64db..77bb369311e 100644 --- a/examples/gno.land/p/jeronimoalbi/datasource/query.gno +++ b/examples/gno.land/p/jeronimoalbi/datasource/query.gno @@ -54,7 +54,7 @@ func ByTag(tag string) QueryOption { // WithFilter assigns a new filter argument to a query. // This option can be used multiple times if more than one // filter has to be given to the query. -func WithFilter(field string, value interface{}) QueryOption { +func WithFilter(field string, value any) QueryOption { return func(q *Query) { q.Filters.Set(field, value) } diff --git a/examples/gno.land/p/jeronimoalbi/datasource/query_test.gno b/examples/gno.land/p/jeronimoalbi/datasource/query_test.gno index 6f78d41bb35..dccce7a8448 100644 --- a/examples/gno.land/p/jeronimoalbi/datasource/query_test.gno +++ b/examples/gno.land/p/jeronimoalbi/datasource/query_test.gno @@ -91,7 +91,7 @@ func TestNewQuery(t *testing.T) { uassert.Equal(t, want.Tag, q.Tag) uassert.Equal(t, want.Filters.Size(), q.Filters.Size()) - want.Filters.Iterate("", "", func(k string, v interface{}) bool { + want.Filters.Iterate("", "", func(k string, v any) bool { got, exists := q.Filters.Get(k) uassert.True(t, exists) if exists { diff --git a/examples/gno.land/p/jeronimoalbi/datastore/datastore.gno b/examples/gno.land/p/jeronimoalbi/datastore/datastore.gno new file mode 100644 index 00000000000..eacce319d49 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/datastore.gno @@ -0,0 +1,50 @@ +package datastore + +import ( + "errors" + + "gno.land/p/demo/avl" +) + +// ErrStorageExists indicates that a storage exists with the same name. +var ErrStorageExists = errors.New("a storage with the same name exists") + +// Datastore is a store that can contain multiple named storages. +// A storage is a collection of records. +// +// Example usage: +// +// // Create an empty storage to store user records +// var db Datastore +// storage := db.CreateStorage("users") +// +// // Get a storage that has been created before +// storage = db.GetStorage("profiles") +type Datastore struct { + storages avl.Tree // string(name) -> *Storage +} + +// CreateStorage creates a new named storage within the data store. +func (ds *Datastore) CreateStorage(name string, options ...StorageOption) *Storage { + if ds.storages.Has(name) { + return nil + } + + s := NewStorage(name, options...) + ds.storages.Set(name, &s) + return &s +} + +// HasStorage checks if data store contains a storage with a specific name. +func (ds Datastore) HasStorage(name string) bool { + return ds.storages.Has(name) +} + +// GetStorage returns a storage that has been created with a specific name. +// It returns nil when a storage with the specified name is not found. +func (ds Datastore) GetStorage(name string) *Storage { + if v, found := ds.storages.Get(name); found { + return v.(*Storage) + } + return nil +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/datastore_test.gno b/examples/gno.land/p/jeronimoalbi/datastore/datastore_test.gno new file mode 100644 index 00000000000..7d522145b36 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/datastore_test.gno @@ -0,0 +1,78 @@ +package datastore + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestDatastoreCreateStorage(t *testing.T) { + cases := []struct { + name string + storageName string + mustFail bool + setup func(*Datastore) + }{ + { + name: "success", + storageName: "users", + }, + { + name: "storage exists", + storageName: "users", + mustFail: true, + setup: func(db *Datastore) { + db.CreateStorage("users") + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var db Datastore + if tc.setup != nil { + tc.setup(&db) + } + + storage := db.CreateStorage(tc.storageName) + + if tc.mustFail { + uassert.Equal(t, nil, storage) + return + } + + urequire.NotEqual(t, nil, storage, "storage created") + uassert.Equal(t, tc.storageName, storage.Name()) + uassert.True(t, db.HasStorage(tc.storageName)) + }) + } +} + +func TestDatastoreHasStorage(t *testing.T) { + var ( + db Datastore + name = "users" + ) + + uassert.False(t, db.HasStorage(name)) + + db.CreateStorage(name) + uassert.True(t, db.HasStorage(name)) +} + +func TestDatastoreGetStorage(t *testing.T) { + var ( + db Datastore + name = "users" + ) + + storage := db.GetStorage(name) + uassert.Equal(t, nil, storage) + + db.CreateStorage(name) + + storage = db.GetStorage(name) + urequire.NotEqual(t, nil, storage, "storage found") + uassert.Equal(t, name, storage.Name()) +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/doc.gno b/examples/gno.land/p/jeronimoalbi/datastore/doc.gno new file mode 100644 index 00000000000..613c7bd7e35 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/doc.gno @@ -0,0 +1,98 @@ +// Package datastore provides support to store multiple collections of records. +// +// It supports the definition of multiple storages, where each one is a collection +// of records. Records can have any number of user defined fields which are added +// dynamically when values are set on a record. These fields can also be renamed +// or removed. +// +// Storages have support for simple schemas that allows users to pre-define fields +// which can optionally have a default value also defined. Default values are +// assigned to new records on creation. +// +// User defined schemas can optionally be strict, which means that records from a +// storage using the schema can only assign values to the pre-defined set of fields. +// In which case, assigning a value to an unknown field would result on an error. +// +// Package also support the definition of custom record indexes. Indexes are used +// by storages to search and iterate records. +// The default index is the ID index but custom single and multi value indexes can +// be defined. +// +// WARNING: Using this package to store your realm data must be carefully considered. +// The fact that record fields are not strictly typed and can be renamed or removed +// could lead to issues if not careful when coding your realm(s). So it's recommended +// that you consider other alternatives first, like alternative patterns or solutions +// provided by the blockchain to deal with data, types and data migration for example. +// +// Example usage: +// +// var db datastore.Datastore +// +// // Define a unique case insensitive index for user emails +// emailIdx := datastore.NewIndex("email", func(r datastore.Record) string { +// return r.MustGet("email").(string) +// }).Unique().CaseInsensitive() +// +// // Create a new storage for user records +// storage := db.CreateStorage("users", datastore.WithIndex(emailIdx)) +// +// // Add a user with a single "email" field +// user := storage.NewRecord() +// user.Set("email", "foo@bar.org") +// +// // Save to assing user ID and update indexes +// user.Save() +// +// // Find user by email using the custom index +// user, _ = storage.Get(emailIdx.Name(), "foo@bar.org") +// +// // Find user by ID +// user, _ = storage.GetByID(user.ID()) +// +// // Search user's profile by email in another existing storage +// storage = db.GetStorage("profiles") +// email := user.MustGet("email").(string) +// profile, found := storage.Get("user", email) +// if !found { +// panic("profile not found") +// } +// +// // Delete the profile from the storage and update indexes +// storage.Delete(profile.ID()) +// +// Example query usage: +// +// var db datastore.Datastore +// +// // Create a query with a custom offset and size +// storage := db.GetStorage("users") +// recordset, err := storage.Query(datastore.WithOffset(100), datastore.WithSize(50)) +// if err != nil { +// panic(err) +// } +// +// // Get all query results +// var records []Record +// recordset.Iterate(func(r datastore.Record) bool { +// records = append(records, r) +// return false +// }) +// +// Example query using a custom index usage: +// +// var db datastore.Datastore +// +// // Create a query to get records using a custom pre-defined index +// storage := db.GetStorage("posts") +// recordset, err := storage.Query(datastore.UseIndex("tags", "tagname")) +// if err != nil { +// panic(err) +// } +// +// // Get all query results +// var records []Record +// recordset.Iterate(func(r datastore.Record) bool { +// records = append(records, r) +// return false +// }) +package datastore diff --git a/examples/gno.land/p/jeronimoalbi/datastore/gno.mod b/examples/gno.land/p/jeronimoalbi/datastore/gno.mod new file mode 100644 index 00000000000..dad2cc4b1ac --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/gno.mod @@ -0,0 +1 @@ +module gno.land/p/jeronimoalbi/datastore diff --git a/examples/gno.land/p/jeronimoalbi/datastore/index.gno b/examples/gno.land/p/jeronimoalbi/datastore/index.gno new file mode 100644 index 00000000000..0bcba34ca96 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/index.gno @@ -0,0 +1,100 @@ +package datastore + +import ( + "gno.land/p/moul/collection" +) + +// DefaultIndexOptions defines the default options for new indexes. +const DefaultIndexOptions = collection.DefaultIndex | collection.SparseIndex + +type ( + // IndexFn defines a type for single value indexing functions. + // This type of function extracts a single string value from + // a record that is then used to index it. + IndexFn func(Record) string + + // IndexMultiValueFn defines a type for multi value indexing functions. + // This type of function extracts multiple string values from a + // record that are then used to index it. + IndexMultiValueFn func(Record) []string + + // Index defines a type for custom user defined storage indexes. + // Storages are by default indexed by the auto geneated record ID + // but can additionally be indexed by other custom record fields. + Index struct { + name string + options collection.IndexOption + fn interface{} + } +) + +// NewIndex creates a new single value index. +// +// Usage example: +// +// // Index a User record by email +// idx := NewIndex("email", func(r Record) string { +// return r.MustGet("email").(string) +// }) +func NewIndex(name string, fn IndexFn) Index { + return Index{ + name: name, + options: DefaultIndexOptions, + fn: func(v interface{}) string { + return fn(v.(Record)) + }, + } +} + +// NewMultiValueIndex creates a new multi value index. +// +// Usage example: +// +// // Index a Post record by tag +// idx := NewMultiValueIndex("tag", func(r Record) []string { +// return r.MustGet("tags").([]string) +// }) +func NewMultiValueIndex(name string, fn IndexMultiValueFn) Index { + return Index{ + name: name, + options: DefaultIndexOptions, + fn: func(v interface{}) []string { + return fn(v.(Record)) + }, + } +} + +// Name returns index's name. +func (idx Index) Name() string { + return idx.name +} + +// Options returns current index options. +// These options define the index behavior regarding case sensitivity and uniquenes. +func (idx Index) Options() collection.IndexOption { + return idx.options +} + +// Func returns the function that storage collections apply +// to each record to get the value to use for indexing it. +func (idx Index) Func() interface{} { + return idx.fn +} + +// Unique returns a copy of the index that indexes record values as unique values. +// Returned index contains previous options plus the unique one. +func (idx Index) Unique() Index { + if idx.options&collection.UniqueIndex == 0 { + idx.options |= collection.UniqueIndex + } + return idx +} + +// CaseInsensitive returns a copy of the index that indexes record values ignoring casing. +// Returned index contains previous options plus the case insensitivity one. +func (idx Index) CaseInsensitive() Index { + if idx.options&collection.CaseInsensitiveIndex == 0 { + idx.options |= collection.CaseInsensitiveIndex + } + return idx +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/index_test.gno b/examples/gno.land/p/jeronimoalbi/datastore/index_test.gno new file mode 100644 index 00000000000..8f90a82c85d --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/index_test.gno @@ -0,0 +1,98 @@ +package datastore + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/moul/collection" +) + +func TestNewIndex(t *testing.T) { + cases := []struct { + name string + options collection.IndexOption + setup func(Index) Index + }{ + { + name: "default", + options: DefaultIndexOptions, + }, + { + name: "unique", + options: DefaultIndexOptions | collection.UniqueIndex, + setup: func(idx Index) Index { return idx.Unique() }, + }, + { + name: "case insensitive", + options: DefaultIndexOptions | collection.CaseInsensitiveIndex, + setup: func(idx Index) Index { return idx.CaseInsensitive() }, + }, + { + name: "unique case insensitive", + options: DefaultIndexOptions | collection.CaseInsensitiveIndex | collection.UniqueIndex, + setup: func(idx Index) Index { return idx.CaseInsensitive().Unique() }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + name := "foo" + idx := NewIndex(name, func(Record) string { return "" }) + + if tc.setup != nil { + idx = tc.setup(idx) + } + + uassert.Equal(t, name, idx.Name()) + uassert.Equal(t, uint64(tc.options), uint64(idx.Options())) + + _, ok := idx.Func().(func(interface{}) string) + uassert.True(t, ok) + }) + } +} + +func TestNewMultiIndex(t *testing.T) { + cases := []struct { + name string + options collection.IndexOption + setup func(Index) Index + }{ + { + name: "default", + options: DefaultIndexOptions, + }, + { + name: "unique", + options: DefaultIndexOptions | collection.UniqueIndex, + setup: func(idx Index) Index { return idx.Unique() }, + }, + { + name: "case insensitive", + options: DefaultIndexOptions | collection.CaseInsensitiveIndex, + setup: func(idx Index) Index { return idx.CaseInsensitive() }, + }, + { + name: "unique case insensitive", + options: DefaultIndexOptions | collection.CaseInsensitiveIndex | collection.UniqueIndex, + setup: func(idx Index) Index { return idx.CaseInsensitive().Unique() }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + name := "foo" + idx := NewMultiValueIndex(name, func(Record) []string { return nil }) + + if tc.setup != nil { + idx = tc.setup(idx) + } + + uassert.Equal(t, name, idx.Name()) + uassert.Equal(t, uint64(tc.options), uint64(idx.Options())) + + _, ok := idx.Func().(func(interface{}) []string) + uassert.True(t, ok) + }) + } +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/query.gno b/examples/gno.land/p/jeronimoalbi/datastore/query.gno new file mode 100644 index 00000000000..85d2e55fe55 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/query.gno @@ -0,0 +1,43 @@ +package datastore + +import ( + "gno.land/p/moul/collection" +) + +var defaultQuery = Query{indexName: collection.IDIndex} + +// Query contains arguments for querying a storage. +type Query struct { + offset int + size int + indexName string + indexKey string +} + +// Offset returns the position of the first record to return. +// The minimum offset value is 0. +func (q Query) Offset() int { + return q.offset +} + +// Size returns the maximum number of records a query returns. +func (q Query) Size() int { + return q.size +} + +// IndexName returns the name of the storage index being used for the query. +func (q Query) IndexName() string { + return q.indexName +} + +// IndexKey return the index key value to locate the records. +// An empty string is returned when all indexed records match the query. +func (q Query) IndexKey() string { + return q.indexKey +} + +// IsEmpty checks if the query is empty. +// Empty queries return no records. +func (q Query) IsEmpty() bool { + return q.indexName == "" +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/query_options.gno b/examples/gno.land/p/jeronimoalbi/datastore/query_options.gno new file mode 100644 index 00000000000..54aaae06a61 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/query_options.gno @@ -0,0 +1,55 @@ +package datastore + +import ( + "errors" + "strings" +) + +var ( + ErrEmptyQueryIndexName = errors.New("query index name is empty") + ErrInvalidQueryOffset = errors.New("minimum allowed query offset is 0") + ErrInvalidQuerySize = errors.New("minimum allowed query size is 1") +) + +// QueryOption configures queries. +type QueryOption func(*Query) error + +// WithOffset assigns the offset or position of the first record that query must return. +// The minimum allowed offset is 0. +func WithOffset(offset int) QueryOption { + return func(q *Query) error { + if offset < 0 { + return ErrInvalidQueryOffset + } + + q.offset = offset + return nil + } +} + +// WithSize assigns the maximum number of records that query can return. +// The minimum allowed size is 1. +func WithSize(size int) QueryOption { + return func(q *Query) error { + if size < 1 { + return ErrInvalidQuerySize + } + + q.size = size + return nil + } +} + +// UseIndex assigns the index that the query must use to get the records. +// Using an index requires a key value to locate the records within the index. +func UseIndex(name, key string) QueryOption { + return func(q *Query) error { + q.indexName = strings.TrimSpace(name) + if q.indexName == "" { + return ErrEmptyQueryIndexName + } + + q.indexKey = key + return nil + } +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/record.gno b/examples/gno.land/p/jeronimoalbi/datastore/record.gno new file mode 100644 index 00000000000..47539bdb759 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/record.gno @@ -0,0 +1,229 @@ +package datastore + +import ( + "errors" + + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" + "gno.land/p/moul/collection" +) + +// ErrUndefinedField indicates that a field in not defined in a record's schema. +var ErrUndefinedField = errors.New("undefined field") + +type ( + // Record stores values for one or more fields. + Record interface { + ReadOnlyRecord + + // Set assings a value to a record field. + // If the field doesn't exist it's created if the underlying schema allows it. + // Storage schema can optionally be strict in which case no new fields other than + // the ones that were previously defined are allowed. + Set(field string, value interface{}) error + + // Save assigns an ID to newly created records and update storage indexes. + Save() bool + } + + // ReadOnlyRecord defines an interface for read-only records. + ReadOnlyRecord interface { + // ID returns record's ID + ID() uint64 + + // Key returns a string representation of the record's ID. + // It's used to be able to search records within the ID index. + Key() string + + // Type returns the record's type. + Type() string + + // Fields returns the list of the record's field names. + Fields() []string + + // IsEmpty checks if the record has no values. + IsEmpty() bool + + // HasField checks if the record has a specific field. + HasField(name string) bool + + // Get returns the value of a record's field. + Get(field string) (value interface{}, found bool) + + // MustGet returns the value of a record's field or panics when the field is not found. + MustGet(field string) interface{} + } + + // RecordIterFn defines a type for record iteration functions. + RecordIterFn func(Record) (stop bool) + + // Recordset defines an interface that allows iterating multiple records. + Recordset interface { + // Iterate iterates records in order. + Iterate(fn RecordIterFn) (stopped bool) + + // ReverseIterate iterates records in reverse order. + ReverseIterate(fn RecordIterFn) (stopped bool) + + // Size returns the number of records in the recordset. + Size() int + } +) + +type record struct { + id uint64 + schema *Schema + collection *collection.Collection + values avl.Tree // string(field index) -> interface{} +} + +// ID returns record's ID +func (r record) ID() uint64 { + return r.id +} + +// Key returns a string representation of the record's ID. +// It's used to be able to search records within the ID index. +func (r record) Key() string { + return seqid.ID(r.id).String() +} + +// Type returns the record's type. +func (r record) Type() string { + return r.schema.Name() +} + +// Fields returns the list of the record's field names. +func (r record) Fields() []string { + return r.schema.Fields() +} + +// IsEmpty checks if the record has no values. +func (r record) IsEmpty() bool { + return r.values.Size() == 0 +} + +// HasField checks if the record has a specific field. +func (r record) HasField(name string) bool { + return r.schema.HasField(name) +} + +// Set assings a value to a record field. +// If the field doesn't exist it's created if the underlying schema allows it. +// Storage schema can optionally be strict in which case no new fields other than +// the ones that were previously defined are allowed. +func (r *record) Set(field string, value interface{}) error { + i := r.schema.GetFieldIndex(field) + if i == -1 { + if r.schema.IsStrict() { + return ErrUndefinedField + } + + i, _ = r.schema.AddField(field, nil) + } + + key := castIntToKey(i) + r.values.Set(key, value) + return nil +} + +// Get returns the value of a record's field. +func (r record) Get(field string) (value interface{}, found bool) { + i := r.schema.GetFieldIndex(field) + if i == -1 { + return nil, false + } + + key := castIntToKey(i) + return r.values.Get(key) +} + +// MustGet returns the value of a record's field or panics when the field is not found. +func (r record) MustGet(field string) interface{} { + v, found := r.Get(field) + if !found { + panic("field not found: " + field) + } + return v +} + +// Save assigns an ID to newly created records and update storage indexes. +func (r *record) Save() bool { + if r.id == 0 { + r.id = r.collection.Set(r) + return r.id != 0 + } + return r.collection.Update(r.id, r) +} + +type recordset struct { + query Query + records avl.ITree + keys []string + size int +} + +// Iterate iterates records in order. +func (rs recordset) Iterate(fn RecordIterFn) (stopped bool) { + if rs.isUsingCustomIndex() { + for _, k := range rs.keys { + v, found := rs.records.Get(k) + if !found { + continue + } + + if fn(v.(Record)) { + return true + } + } + + return false + } + + offset := rs.query.Offset() + count := rs.query.Size() + if count == 0 { + count = rs.records.Size() + } + + return rs.records.IterateByOffset(offset, count, func(_ string, v interface{}) bool { + return fn(v.(Record)) + }) +} + +// ReverseIterate iterates records in reverse order. +func (rs recordset) ReverseIterate(fn RecordIterFn) (stopped bool) { + if rs.isUsingCustomIndex() { + for i := len(rs.keys) - 1; i >= 0; i-- { + v, found := rs.records.Get(rs.keys[i]) + if !found { + continue + } + + if fn(v.(Record)) { + return true + } + } + + return false + } + + offset := rs.query.Offset() + count := rs.query.Size() + if count == 0 { + count = rs.records.Size() + } + + return rs.records.ReverseIterateByOffset(offset, count, func(_ string, v interface{}) bool { + return fn(v.(Record)) + }) +} + +// Size returns the number of records in the recordset. +func (rs recordset) Size() int { + return rs.size +} + +func (rs recordset) isUsingCustomIndex() bool { + return rs.query.IndexName() != collection.IDIndex +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/record_test.gno b/examples/gno.land/p/jeronimoalbi/datastore/record_test.gno new file mode 100644 index 00000000000..1efd8236857 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/record_test.gno @@ -0,0 +1,337 @@ +package datastore + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +var ( + _ Record = (*record)(nil) + _ Recordset = (*recordset)(nil) +) + +func TestRecordDefaults(t *testing.T) { + // Arrange + storage := NewStorage("foo") + + // Act + r := storage.NewRecord() + + // Assert + uassert.Equal(t, uint64(0), r.ID()) + uassert.Equal(t, "0000000", r.Key()) + uassert.Equal(t, "Foo", r.Type()) + uassert.Equal(t, nil, r.Fields()) + uassert.True(t, r.IsEmpty()) +} + +func TestRecordHasField(t *testing.T) { + storage := NewStorage("foo") + storage.Schema().AddField("foo", nil) + + r := storage.NewRecord() + + uassert.True(t, r.HasField("foo")) + uassert.False(t, r.HasField("undefined")) +} + +func TestRecordSet(t *testing.T) { + cases := []struct { + name string + options []SchemaOption + field string + fieldsCount int + value int + err error + }{ + { + name: "first new field", + field: "test", + fieldsCount: 1, + value: 42, + }, + { + name: "new extra field", + options: []SchemaOption{ + WithField("foo"), + WithField("bar"), + }, + field: "test", + fieldsCount: 3, + value: 42, + }, + { + name: "existing field", + options: []SchemaOption{ + WithField("test"), + }, + field: "test", + fieldsCount: 1, + value: 42, + }, + { + name: "undefined field", + options: []SchemaOption{Strict()}, + field: "test", + fieldsCount: 1, + value: 42, + err: ErrUndefinedField, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Arrange + s := NewSchema("Foo", tc.options...) + storage := NewStorage("foo", WithSchema(s)) + r := storage.NewRecord() + + // Act + err := r.Set(tc.field, tc.value) + + // Assert + if tc.err != nil { + urequire.ErrorIs(t, err, tc.err) + return + } + + urequire.NoError(t, err) + uassert.True(t, r.HasField("test")) + uassert.False(t, r.IsEmpty()) + uassert.Equal(t, tc.fieldsCount, len(r.Fields())) + }) + } +} + +func TestRecordGet(t *testing.T) { + storage := NewStorage("foo") + r := storage.NewRecord() + r.Set("foo", "bar") + r.Set("test", 42) + + v, found := r.Get("test") + urequire.True(t, found, "get setted value") + + got, ok := v.(int) + urequire.True(t, ok, "setted value type") + uassert.Equal(t, 42, got) + + _, found = r.Get("unknown") + uassert.False(t, found) +} + +func TestRecordSave(t *testing.T) { + index := NewIndex("name", func(r Record) string { + return r.MustGet("name").(string) + }).Unique().CaseInsensitive() + + storage := NewStorage("foo", WithIndex(index)) + cases := []struct { + name string + id uint64 + fieldValue, key string + storageSize int + setup func(Storage) Record + }{ + { + name: "create first record", + id: 1, + key: "0000001", + fieldValue: "foo", + storageSize: 1, + setup: func(s Storage) Record { return s.NewRecord() }, + }, + { + name: "create second record", + id: 2, + key: "0000002", + fieldValue: "bar", + storageSize: 2, + setup: func(s Storage) Record { return s.NewRecord() }, + }, + { + name: "update second record", + id: 2, + key: "0000002", + fieldValue: "baz", + storageSize: 2, + setup: func(s Storage) Record { + r, _ := storage.Get(index.Name(), "bar") + return r + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + r := tc.setup(storage) + r.Set("name", tc.fieldValue) + + _, found := storage.Get(index.Name(), tc.fieldValue) + urequire.False(t, found, "record not found") + urequire.True(t, r.Save(), "save success") + uassert.Equal(t, tc.storageSize, storage.Size()) + + r, found = storage.Get(index.Name(), tc.fieldValue) + urequire.True(t, found, "record found") + uassert.Equal(t, tc.id, r.ID()) + uassert.Equal(t, tc.key, r.Key()) + uassert.Equal(t, tc.fieldValue, r.MustGet("name")) + }) + } +} + +func TestRecordsetIterate(t *testing.T) { + cases := []struct { + name string + recordIDs []uint64 + setup func(*Storage) + }{ + { + name: "single record", + recordIDs: []uint64{1}, + setup: func(s *Storage) { + s.NewRecord().Save() + }, + }, + { + name: "two records", + recordIDs: []uint64{1, 2}, + setup: func(s *Storage) { + s.NewRecord().Save() + s.NewRecord().Save() + }, + }, + { + name: "empty recordset", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Arrange + storage := NewStorage("foo") + if tc.setup != nil { + tc.setup(&storage) + } + + var ( + records []Record + rs = storage.MustQuery() + ) + + // Act + rs.Iterate(func(r Record) bool { + records = append(records, r) + return false + }) + + // Assert + urequire.Equal(t, len(tc.recordIDs), len(records), "results count") + for i, r := range records { + uassert.Equal(t, tc.recordIDs[i], r.ID()) + } + }) + } +} + +func TestRecordsetReverseIterate(t *testing.T) { + cases := []struct { + name string + recordIDs []uint64 + setup func(*Storage) + }{ + { + name: "single record", + recordIDs: []uint64{1}, + setup: func(s *Storage) { + s.NewRecord().Save() + }, + }, + { + name: "two records", + recordIDs: []uint64{2, 1}, + setup: func(s *Storage) { + s.NewRecord().Save() + s.NewRecord().Save() + }, + }, + { + name: "empty recordser", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Arrange + storage := NewStorage("foo") + if tc.setup != nil { + tc.setup(&storage) + } + + var ( + records []Record + rs = storage.MustQuery() + ) + + // Act + rs.ReverseIterate(func(r Record) bool { + records = append(records, r) + return false + }) + + // Assert + urequire.Equal(t, len(tc.recordIDs), len(records), "results count") + for i, r := range records { + uassert.Equal(t, tc.recordIDs[i], r.ID()) + } + }) + } +} + +func TestRecordsetSize(t *testing.T) { + cases := []struct { + name string + size int + setup func(*Storage) + }{ + { + name: "single record", + size: 1, + setup: func(s *Storage) { + s.NewRecord().Save() + }, + }, + { + name: "two records", + size: 2, + setup: func(s *Storage) { + s.NewRecord().Save() + s.NewRecord().Save() + }, + }, + { + name: "empty recordser", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Arrange + storage := NewStorage("foo") + if tc.setup != nil { + tc.setup(&storage) + } + + rs := storage.MustQuery() + + // Act + size := rs.Size() + + // Assert + uassert.Equal(t, tc.size, size) + }) + } +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/schema.gno b/examples/gno.land/p/jeronimoalbi/datastore/schema.gno new file mode 100644 index 00000000000..89abf2bc695 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/schema.gno @@ -0,0 +1,149 @@ +package datastore + +import ( + "encoding/binary" + + "gno.land/p/demo/avl" + "gno.land/p/demo/avl/list" +) + +// TODO: Support versioning + +// Schema contains information about fields and default field values. +// It also offers the possibility to configure it as static to indicate +// that only configured fields should be allowed. +type Schema struct { + name string + strict bool + fields list.List // int(field index) -> string(field name) + defaults avl.Tree // string(field index) -> interface{} +} + +// NewSchema creates a new schema. +func NewSchema(name string, options ...SchemaOption) *Schema { + s := &Schema{name: name} + for _, apply := range options { + apply(s) + } + return s +} + +// Name returns schema's name. +func (s Schema) Name() string { + return s.name +} + +// Fields returns the list field names that are defined in the schema. +func (s Schema) Fields() []string { + fields := make([]string, s.fields.Len()) + s.fields.ForEach(func(i int, v interface{}) bool { + fields[i] = v.(string) + return false + }) + return fields +} + +// Size returns the number of fields the schema has. +func (s Schema) Size() int { + return s.fields.Len() +} + +// IsStrict check if the schema is configured as a strict one. +func (s Schema) IsStrict() bool { + return s.strict +} + +// HasField check is a field has been defined in the schema. +func (s Schema) HasField(name string) bool { + return s.GetFieldIndex(name) >= 0 +} + +// AddField adds a new field to the schema. +// A default field value can be specified, otherwise `defaultValue` must be nil. +func (s *Schema) AddField(name string, defaultValue interface{}) (index int, added bool) { + if s.HasField(name) { + return -1, false + } + + s.fields.Append(name) + index = s.fields.Len() - 1 + if defaultValue != nil { + key := castIntToKey(index) + s.defaults.Set(key, defaultValue) + } + return index, true +} + +// GetFieldIndex returns the index number of a schema field. +// +// Field index indicates the order the field has within the schema. +// When defined fields are added they get an index starting from +// field index 0. +// +// Fields are internally referenced by index number instead of the name +// to be able to rename fields easily. +func (s Schema) GetFieldIndex(name string) int { + index := -1 + s.fields.ForEach(func(i int, v interface{}) bool { + if name != v.(string) { + return false + } + + index = i + return true + }) + return index +} + +// GetFieldName returns the name of a field for a specific field index. +func (s Schema) GetFieldName(index int) (name string, found bool) { + v := s.fields.Get(index) + if v == nil { + return "", false + } + return v.(string), true +} + +// GetDefault returns the default value for a field. +func (s Schema) GetDefault(name string) (value interface{}, found bool) { + i := s.GetFieldIndex(name) + if i == -1 { + return nil, false + } + return s.GetDefaultByIndex(i) +} + +// GetDefaultByIndex returns the default value for a field by it's index. +func (s Schema) GetDefaultByIndex(index int) (value interface{}, found bool) { + key := castIntToKey(index) + v, found := s.defaults.Get(key) + if !found { + return nil, false + } + + if fn, ok := v.(func() interface{}); ok { + return fn(), true + } + return v, true +} + +// RenameField renames a field. +func (s *Schema) RenameField(name, newName string) (renamed bool) { + if s.HasField(newName) { + return false + } + + i := s.GetFieldIndex(name) + if i == -1 { + return false + } + + s.fields.Set(i, newName) + return true +} + +func castIntToKey(i int) string { + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, uint64(i)) + return string(buf) +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/schema_options.gno b/examples/gno.land/p/jeronimoalbi/datastore/schema_options.gno new file mode 100644 index 00000000000..78054b13255 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/schema_options.gno @@ -0,0 +1,41 @@ +package datastore + +import "strings" + +// StorageOption configures schemas. +type SchemaOption func(*Schema) + +// WithField assign a new field to the schema definition. +func WithField(name string) SchemaOption { + return func(s *Schema) { + name = strings.TrimSpace(name) + if name != "" { + s.fields.Append(name) + } + } +} + +// WithDefaultField assign a new field with a default value to the schema definition. +// Default value is assigned to newly created records asociated to to schema. +func WithDefaultField(name string, value interface{}) SchemaOption { + return func(s *Schema) { + name = strings.TrimSpace(name) + if name != "" { + s.fields.Append(name) + + key := castIntToKey(s.fields.Len() - 1) + s.defaults.Set(key, value) + } + } +} + +// Strict configures the schema as a strict one. +// By default schemas should allow the creation of any user defined field, +// making them strict limits the allowed record fields to the ones pre-defined +// in the schema. Fields are pre-defined using `WithField`, `WithDefaultField` +// or by calling `Schema.AddField()`. +func Strict() SchemaOption { + return func(s *Schema) { + s.strict = true + } +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/schema_test.gno b/examples/gno.land/p/jeronimoalbi/datastore/schema_test.gno new file mode 100644 index 00000000000..f764d85dba2 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/schema_test.gno @@ -0,0 +1,210 @@ +package datastore + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestSchemaNew(t *testing.T) { + cases := []struct { + name string + options []SchemaOption + fields []string + strict bool + }{ + { + name: "default", + }, + { + name: "strict", + options: []SchemaOption{Strict()}, + strict: true, + }, + { + name: "with fields", + options: []SchemaOption{ + WithField("foo"), + WithField("bar"), + WithDefaultField("baz", 42), + }, + fields: []string{"foo", "bar", "baz"}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + s := NewSchema("Foo", tc.options...) + + uassert.Equal(t, "Foo", s.Name()) + uassert.Equal(t, tc.strict, s.IsStrict()) + urequire.Equal(t, len(tc.fields), s.Size(), "field count") + + for i, name := range s.Fields() { + uassert.Equal(t, tc.fields[i], name) + uassert.True(t, s.HasField(name)) + } + }) + } +} + +func TestSchemaAddField(t *testing.T) { + cases := []struct { + name string + options []SchemaOption + fieldName string + fieldIndex int + fields []string + success bool + }{ + { + name: "new only field", + fieldName: "foo", + fieldIndex: 0, + fields: []string{"foo"}, + success: true, + }, + { + name: "new existing fields", + options: []SchemaOption{ + WithField("foo"), + WithField("bar"), + }, + fieldName: "baz", + fieldIndex: 2, + fields: []string{"foo", "bar", "baz"}, + success: true, + }, + { + name: "duplicated field", + options: []SchemaOption{WithField("foo")}, + fieldName: "foo", + fieldIndex: -1, + fields: []string{"foo"}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + s := NewSchema("Foo", tc.options...) + + index, added := s.AddField(tc.fieldName, nil) + + if tc.success { + uassert.Equal(t, tc.fieldIndex, index) + uassert.True(t, added) + } else { + uassert.Equal(t, -1, index) + uassert.False(t, added) + } + + urequire.Equal(t, len(tc.fields), s.Size(), "field count") + + for i, name := range s.Fields() { + uassert.Equal(t, tc.fields[i], name) + uassert.True(t, s.HasField(name)) + } + }) + } +} + +func TestSchemaGetFieldIndex(t *testing.T) { + s := NewSchema("Foo") + s.AddField("foo", nil) + s.AddField("bar", nil) + s.AddField("baz", nil) + + uassert.Equal(t, 0, s.GetFieldIndex("foo")) + uassert.Equal(t, 1, s.GetFieldIndex("bar")) + uassert.Equal(t, 2, s.GetFieldIndex("baz")) + + uassert.Equal(t, -1, s.GetFieldIndex("")) + uassert.Equal(t, -1, s.GetFieldIndex("unknown")) +} + +func TestSchemaGetFieldName(t *testing.T) { + s := NewSchema("Foo") + s.AddField("foo", nil) + s.AddField("bar", nil) + s.AddField("baz", nil) + + name, found := s.GetFieldName(0) + uassert.Equal(t, "foo", name) + uassert.True(t, found) + + name, found = s.GetFieldName(1) + uassert.Equal(t, "bar", name) + uassert.True(t, found) + + name, found = s.GetFieldName(2) + uassert.Equal(t, "baz", name) + uassert.True(t, found) + + name, found = s.GetFieldName(404) + uassert.Equal(t, "", name) + uassert.False(t, found) +} + +func TestSchemaGetDefault(t *testing.T) { + s := NewSchema("Foo") + s.AddField("foo", nil) + s.AddField("bar", 42) + + _, found := s.GetDefault("foo") + uassert.False(t, found) + + v, found := s.GetDefault("bar") + uassert.True(t, found) + + got, ok := v.(int) + urequire.True(t, ok, "default field value") + uassert.Equal(t, 42, got) +} + +func TestSchemaGetDefaultByIndex(t *testing.T) { + s := NewSchema("Foo") + s.AddField("foo", nil) + s.AddField("bar", 42) + + _, found := s.GetDefaultByIndex(0) + uassert.False(t, found) + + _, found = s.GetDefaultByIndex(404) + uassert.False(t, found) + + v, found := s.GetDefaultByIndex(1) + uassert.True(t, found) + + got, ok := v.(int) + urequire.True(t, ok, "default field value") + uassert.Equal(t, 42, got) +} + +func TestSchemaRenameField(t *testing.T) { + s := NewSchema("Foo") + s.AddField("foo", nil) + s.AddField("bar", nil) + + renamed := s.RenameField("foo", "bar") + uassert.False(t, renamed) + + renamed = s.RenameField("", "baz") + uassert.False(t, renamed) + + renamed = s.RenameField("foo", "") + uassert.True(t, renamed) + + renamed = s.RenameField("", "foo") + uassert.True(t, renamed) + + renamed = s.RenameField("foo", "foobar") + uassert.True(t, renamed) + + urequire.Equal(t, 2, s.Size(), "field count") + fields := []string{"foobar", "bar"} + for i, name := range s.Fields() { + uassert.Equal(t, fields[i], name) + uassert.True(t, s.HasField(name)) + } +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/storage.gno b/examples/gno.land/p/jeronimoalbi/datastore/storage.gno new file mode 100644 index 00000000000..7f5e941650c --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/storage.gno @@ -0,0 +1,234 @@ +package datastore + +import ( + "errors" + "strings" + + "gno.land/p/demo/seqid" + "gno.land/p/moul/collection" +) + +// NewStorage creates a new records storage. +func NewStorage(name string, options ...StorageOption) Storage { + s := Storage{ + name: name, + collection: collection.New(), + schema: NewSchema(strings.Title(name)), + } + + for _, apply := range options { + apply(&s) + } + return s +} + +// Storage stores a collection of records. +// +// By default it searches records by record ID but it allows +// using custom user defined indexes for other record fields. +// +// When a storage is created it defines a default schema that +// keeps track of record fields. Storage can be optionally +// created with a user defined schema in cases where the number +// of fields has to be pre-defined or when new records must have +// one or more fields initialized to default values. +type Storage struct { + name string + collection *collection.Collection + schema *Schema +} + +// Name returns storage's name. +func (s Storage) Name() string { + return s.name +} + +// Collection returns the undelying collection used by the +// storage to store all records. +func (s Storage) Collection() *collection.Collection { + return s.collection +} + +// Schema returns the schema being used to track record fields. +func (s Storage) Schema() *Schema { + return s.schema +} + +// Size returns the number of records that the storage have. +func (s Storage) Size() int { + return s.collection.GetIndex(collection.IDIndex).Size() +} + +// NewRecord creates a new storage record. +// +// If a custom schema with default field values is assigned to +// storage it's used to assign initial default values when new +// records are created. +// +// Creating a new record doesn't assign an ID to it, a new ID +// is generated and assigned to the record when it's saved for +// the first time. +func (s Storage) NewRecord() Record { + r := &record{ + schema: s.schema, + collection: s.collection, + } + + // Assign default record values if the schema defines them + for i, name := range s.schema.Fields() { + if v, found := s.schema.GetDefaultByIndex(i); found { + r.Set(name, v) + } + } + return r +} + +// Query returns a recordset that matches the query parameters. +// By default query selects records using the ID index. +// +// Example usage: +// +// // Get 50 records starting from the one at position 100 +// rs, _ := storage.Query( +// WithOffset(100), +// WithSize(50), +// ) +// +// // Iterate records to create a new slice +// var records []Record +// rs.Iterate(func (r Record) bool { +// records = append(records, r) +// return false +// }) +func (s Storage) Query(options ...QueryOption) (Recordset, error) { + // Initialize the recordset for the query + rs := recordset{ + query: defaultQuery, + records: s.collection.GetIndex(collection.IDIndex), + } + + for _, apply := range options { + if err := apply(&rs.query); err != nil { + return nil, err + } + } + + indexName := rs.query.IndexName() + if indexName != collection.IDIndex { + // When using a custom index get the keys to get records from the ID index + keys, err := s.getIndexRecordsKeys(indexName, rs.query.IndexKey()) + if err != nil { + return nil, err + } + + // Adjust the number of keys to match available query options + if offset := rs.query.Offset(); offset > 0 { + if offset > len(keys) { + keys = nil + } else { + keys = keys[offset:] + } + } + + if size := rs.query.Size(); size > 0 && size < len(keys) { + keys = keys[:size] + } + + rs.keys = keys + rs.size = len(keys) + } else { + // When using the default ID index init size with the total number of records + rs.size = rs.records.Size() + + // Adjust recordset size to match available query options + if offset := rs.query.Offset(); offset > 0 { + if offset > rs.size { + rs.size = 0 + } else { + rs.size -= offset + } + } + + if size := rs.query.Size(); size > 0 && size < rs.size { + rs.size = size + } + } + + return rs, nil +} + +// MustQuery returns a recordset that matches the query parameters or panics on error. +// By default query selects records using the ID index. +// +// Example usage: +// +// // Get 50 records starting from the one at position 100 +// var records []Record +// storage.MustQuery( +// WithOffset(100), +// WithSize(50), +// ).Iterate(func (r Record) bool { +// records = append(records, r) +// return false +// }) +func (s Storage) MustQuery(options ...QueryOption) Recordset { + rs, err := s.Query(options...) + if err != nil { + panic(err) + } + return rs +} + +// Get returns the first record found for a key within a storage index. +// +// This is a convenience method to get a single record. A multi index will +// always return the first record value for the specified key in this case. +// To get multiple records create a query using a custom index and key value +// or use the underlying storage collection. +func (s Storage) Get(indexName, indexKey string) (_ Record, found bool) { + iter := s.collection.Get(indexName, indexKey) + if iter.Next() { + return iter.Value().Obj.(Record), true + } + return nil, false +} + +// GetByID returns a record whose ID matches the specified ID. +func (s Storage) GetByID(id uint64) (_ Record, found bool) { + iter := s.collection.Get(collection.IDIndex, seqid.ID(id).String()) + if iter.Next() { + return iter.Value().Obj.(Record), true + } + return nil, false +} + +// Delete deletes a record from the storage. +func (s Storage) Delete(id uint64) bool { + return s.collection.Delete(id) +} + +func (s Storage) getIndexRecordsKeys(indexName, indexKey string) ([]string, error) { + idx := s.collection.GetIndex(indexName) + if idx == nil { + return nil, errors.New("storage index for query not found: " + indexName) + } + + var keys []string + if v, found := idx.Get(indexKey); found { + keys = castIfaceToRecordKeys(v) + if keys == nil { + return nil, errors.New("unexpected storage index key format") + } + } + return keys, nil +} + +func castIfaceToRecordKeys(v interface{}) []string { + switch k := v.(type) { + case []string: + return k + case string: + return []string{k} + } + return nil +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/storage_options.gno b/examples/gno.land/p/jeronimoalbi/datastore/storage_options.gno new file mode 100644 index 00000000000..73ab525eb67 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/storage_options.gno @@ -0,0 +1,20 @@ +package datastore + +// StorageOption configures storages. +type StorageOption func(*Storage) + +// WithSchema assigns a schema to the storage. +func WithSchema(s *Schema) StorageOption { + return func(st *Storage) { + if s != nil { + st.schema = s + } + } +} + +// WithIndex assigns an index to the storage. +func WithIndex(i Index) StorageOption { + return func(st *Storage) { + st.collection.AddIndex(i.name, i.fn, i.options) + } +} diff --git a/examples/gno.land/p/jeronimoalbi/datastore/storage_test.gno b/examples/gno.land/p/jeronimoalbi/datastore/storage_test.gno new file mode 100644 index 00000000000..8d7b79cf803 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/datastore/storage_test.gno @@ -0,0 +1,375 @@ +package datastore + +import ( + "strings" + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestStorageDefaults(t *testing.T) { + name := "foo" + storage := NewStorage(name) + + uassert.Equal(t, name, storage.Name()) + uassert.NotEqual(t, nil, storage.Collection()) + uassert.Equal(t, 0, storage.Size()) + + s := storage.Schema() + uassert.NotEqual(t, nil, s) + uassert.Equal(t, strings.Title(name), s.Name()) +} + +func TestStorageNewRecord(t *testing.T) { + field := "status" + defaultValue := "testing" + s := NewSchema("Foo", WithDefaultField(field, defaultValue)) + storage := NewStorage("foo", WithSchema(s)) + + r := storage.NewRecord() + urequire.NotEqual(t, nil, r, "new record is not nil") + uassert.Equal(t, uint64(0), r.ID()) + uassert.Equal(t, storage.Schema().Name(), r.Type()) + + v, found := r.Get(field) + urequire.True(t, found, "default value found") + + got, ok := v.(string) + urequire.True(t, ok, "default value type") + uassert.Equal(t, defaultValue, got) +} + +func TestStorageQuery(t *testing.T) { + index := NewIndex("tag", func(r Record) string { + if v, found := r.Get("tag"); found { + return v.(string) + } + return "" + }) + + cases := []struct { + name string + options []QueryOption + results []uint64 + setup func() *Storage + errMsg string + }{ + { + name: "default query", + results: []uint64{1, 2}, + setup: func() *Storage { + s := NewStorage("foo") + s.NewRecord().Save() + s.NewRecord().Save() + return &s + }, + }, + { + name: "with size", + results: []uint64{1}, + options: []QueryOption{WithSize(1)}, + setup: func() *Storage { + s := NewStorage("foo") + s.NewRecord().Save() + s.NewRecord().Save() + return &s + }, + }, + { + name: "with offset", + results: []uint64{2}, + options: []QueryOption{WithOffset(1)}, + setup: func() *Storage { + s := NewStorage("foo") + s.NewRecord().Save() + s.NewRecord().Save() + return &s + }, + }, + { + name: "with offset overflow", + options: []QueryOption{WithOffset(4)}, + setup: func() *Storage { + s := NewStorage("foo") + s.NewRecord().Save() + return &s + }, + }, + { + name: "with size and offset", + results: []uint64{2, 3}, + options: []QueryOption{WithSize(2), WithOffset(1)}, + setup: func() *Storage { + s := NewStorage("foo") + s.NewRecord().Save() + s.NewRecord().Save() + s.NewRecord().Save() + s.NewRecord().Save() + return &s + }, + }, + { + name: "custom index", + options: []QueryOption{UseIndex("tag", "A")}, + results: []uint64{1, 3}, + setup: func() *Storage { + s := NewStorage("foo", WithIndex(index)) + + r := s.NewRecord() + r.Set("tag", "A") + r.Save() + + r = s.NewRecord() + r.Set("tag", "B") + r.Save() + + r = s.NewRecord() + r.Set("tag", "A") + r.Save() + + return &s + }, + }, + { + name: "custom index with offset", + options: []QueryOption{UseIndex("tag", "B"), WithOffset(1)}, + results: []uint64{3, 4}, + setup: func() *Storage { + s := NewStorage("foo", WithIndex(index)) + + r := s.NewRecord() + r.Set("tag", "B") + r.Save() + + r = s.NewRecord() + r.Set("tag", "A") + r.Save() + + r = s.NewRecord() + r.Set("tag", "B") + r.Save() + + r = s.NewRecord() + r.Set("tag", "B") + r.Save() + + return &s + }, + }, + { + name: "custom index with offset and size", + options: []QueryOption{UseIndex("tag", "B"), WithOffset(1), WithSize(1)}, + results: []uint64{3}, + setup: func() *Storage { + s := NewStorage("foo", WithIndex(index)) + + r := s.NewRecord() + r.Set("tag", "B") + r.Save() + + r = s.NewRecord() + r.Set("tag", "A") + r.Save() + + r = s.NewRecord() + r.Set("tag", "B") + r.Save() + + r = s.NewRecord() + r.Set("tag", "B") + r.Save() + + return &s + }, + }, + { + name: "custom index not found", + options: []QueryOption{UseIndex("foo", "B")}, + setup: func() *Storage { + s := NewStorage("foo") + return &s + }, + errMsg: "storage index for query not found: foo", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Arrange + storage := tc.setup() + + // Act + rs, err := storage.Query(tc.options...) + + // Assert + if tc.errMsg != "" { + uassert.ErrorContains(t, err, tc.errMsg, "expect error") + return + } + + urequire.NoError(t, err, "expect no error") + urequire.NotEqual(t, nil, rs, "new record is not nil") + urequire.Equal(t, len(tc.results), rs.Size(), "expect query results count to match") + + var i int + rs.Iterate(func(r Record) bool { + urequire.Equal(t, tc.results[i], r.ID(), "expect result IDs to match") + i++ + return false + }) + }) + } +} + +func TestStorageGet(t *testing.T) { + index := NewIndex("name", func(r Record) string { + if v, found := r.Get("name"); found { + return v.(string) + } + return "" + }) + + cases := []struct { + name string + key string + recordID uint64 + setup func(*Storage) + }{ + { + name: "single record", + key: "foobar", + recordID: 1, + setup: func(s *Storage) { + r := s.NewRecord() + r.Set("name", "foobar") + r.Save() + }, + }, + { + name: "two records", + key: "foobar", + recordID: 1, + setup: func(s *Storage) { + r := s.NewRecord() + r.Set("name", "foobar") + r.Save() + + r = s.NewRecord() + r.Set("name", "foobar") + r.Save() + + r = s.NewRecord() + r.Set("name", "extra") + r.Save() + }, + }, + { + name: "record not found", + key: "unknown", + setup: func(s *Storage) { + r := s.NewRecord() + r.Set("name", "foobar") + r.Save() + }, + }, + { + name: "empty storage", + key: "foobar", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + storage := NewStorage("foo", WithIndex(index)) + if tc.setup != nil { + tc.setup(&storage) + } + + r, found := storage.Get(index.Name(), tc.key) + + if tc.recordID == 0 { + uassert.Equal(t, nil, r, "expect no record") + uassert.False(t, found, "expect record not found") + return + } + + uassert.True(t, found, "expect record found") + urequire.NotEqual(t, nil, r, "expect record to be found") + uassert.Equal(t, tc.recordID, r.ID(), "expect ID to match") + }) + } +} + +func TestStorageGetByID(t *testing.T) { + cases := []struct { + name string + recordID uint64 + found bool + setup func(*Storage) + }{ + { + name: "single record", + recordID: 1, + found: true, + setup: func(s *Storage) { + s.NewRecord().Save() + }, + }, + { + name: "multiple records", + recordID: 2, + found: true, + setup: func(s *Storage) { + s.NewRecord().Save() + s.NewRecord().Save() + s.NewRecord().Save() + }, + }, + { + name: "record not found", + recordID: 3, + setup: func(s *Storage) { + s.NewRecord().Save() + s.NewRecord().Save() + }, + }, + { + name: "empty storage", + recordID: 1, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + storage := NewStorage("foo") + if tc.setup != nil { + tc.setup(&storage) + } + + r, found := storage.GetByID(tc.recordID) + + if !tc.found { + uassert.Equal(t, nil, r, "expect no record") + uassert.False(t, found, "expect record not found") + return + } + + uassert.True(t, found, "expect record found") + urequire.NotEqual(t, nil, r, "expect record to be found") + uassert.Equal(t, tc.recordID, r.ID(), "expect ID to match") + }) + } +} + +func TestStorageDelete(t *testing.T) { + storage := NewStorage("foo") + r := storage.NewRecord() + r.Save() + + deleted := storage.Delete(r.ID()) + uassert.True(t, deleted) + + deleted = storage.Delete(r.ID()) + uassert.False(t, deleted) +} diff --git a/examples/gno.land/p/jeronimoalbi/pager/gno.mod b/examples/gno.land/p/jeronimoalbi/pager/gno.mod new file mode 100644 index 00000000000..e775954b9fe --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/pager/gno.mod @@ -0,0 +1 @@ +module gno.land/p/jeronimoalbi/pager diff --git a/examples/gno.land/p/jeronimoalbi/pager/pager.gno b/examples/gno.land/p/jeronimoalbi/pager/pager.gno new file mode 100644 index 00000000000..7b8a1948b3b --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/pager/pager.gno @@ -0,0 +1,204 @@ +// Package pager provides pagination functionality through a generic pager implementation. +// +// Example usage: +// +// import ( +// "strconv" +// "strings" +// +// "gno.land/p/jeronimoalbi/pager" +// ) +// +// func Render(path string) string { +// // Define the items to paginate +// items := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} +// +// // Create a pager that paginates 4 items at a time +// p, err := pager.New(path, len(items), pager.WithPageSize(4)) +// if err != nil { +// panic(err) +// } +// +// // Render items for the current page +// var output strings.Builder +// p.Iterate(func(i int) bool { +// output.WriteString("- " + strconv.Itoa(items[i]) + "\n") +// return false +// }) +// +// // Render page picker +// if p.HasPages() { +// output.WriteString("\n" + pager.Picker(p)) +// } +// +// return output.String() +// } +package pager + +import ( + "errors" + "math" + "net/url" + "strconv" + "strings" +) + +var ErrInvalidPageNumber = errors.New("invalid page number") + +// PagerIterFn defines a callback to iterate page items. +type PagerIterFn func(index int) (stop bool) + +// New creates a new pager. +func New(rawURL string, totalItems int, options ...PagerOption) (Pager, error) { + u, err := url.Parse(rawURL) + if err != nil { + return Pager{}, err + } + + p := Pager{ + query: u.RawQuery, + pageQueryParam: DefaultPageQueryParam, + pageSize: DefaultPageSize, + page: 1, + totalItems: totalItems, + } + for _, apply := range options { + apply(&p) + } + + p.pageCount = int(math.Ceil(float64(p.totalItems) / float64(p.pageSize))) + + rawPage := u.Query().Get(p.pageQueryParam) + if rawPage != "" { + p.page, _ = strconv.Atoi(rawPage) + if p.page == 0 || p.page > p.pageCount { + return Pager{}, ErrInvalidPageNumber + } + } + + return p, nil +} + +// MustNew creates a new pager or panics if there is an error. +func MustNew(rawURL string, totalItems int, options ...PagerOption) Pager { + p, err := New(rawURL, totalItems, options...) + if err != nil { + panic(err) + } + return p +} + +// Pager allows paging items. +type Pager struct { + query, pageQueryParam string + pageSize, page, pageCount, totalItems int +} + +// TotalItems returns the total number of items to paginate. +func (p Pager) TotalItems() int { + return p.totalItems +} + +// PageSize returns the size of each page. +func (p Pager) PageSize() int { + return p.pageSize +} + +// Page returns the current page number. +func (p Pager) Page() int { + return p.page +} + +// PageCount returns the number pages. +func (p Pager) PageCount() int { + return p.pageCount +} + +// Offset returns the index of the first page item. +func (p Pager) Offset() int { + return (p.page - 1) * p.pageSize +} + +// HasPages checks if pager has more than one page. +func (p Pager) HasPages() bool { + return p.pageCount > 1 +} + +// GetPageURI returns the URI for a page. +// An empty string is returned when page doesn't exist. +func (p Pager) GetPageURI(page int) string { + if page < 1 || page > p.PageCount() { + return "" + } + + values, _ := url.ParseQuery(p.query) + values.Set(p.pageQueryParam, strconv.Itoa(page)) + return "?" + values.Encode() +} + +// PrevPageURI returns the URI path to the previous page. +// An empty string is returned when current page is the first page. +func (p Pager) PrevPageURI() string { + if p.page == 1 || !p.HasPages() { + return "" + } + return p.GetPageURI(p.page - 1) +} + +// NextPageURI returns the URI path to the next page. +// An empty string is returned when current page is the last page. +func (p Pager) NextPageURI() string { + if p.page == p.pageCount { + // Current page is the last page + return "" + } + return p.GetPageURI(p.page + 1) +} + +// Iterate allows iterating page items. +func (p Pager) Iterate(fn PagerIterFn) bool { + if p.totalItems == 0 { + return true + } + + start := p.Offset() + end := start + p.PageSize() + if end > p.totalItems { + end = p.totalItems + } + + for i := start; i < end; i++ { + if fn(i) { + return true + } + } + return false +} + +// TODO: Support different types of pickers (ex. with clickable page numbers) + +// Picker returns a string with the pager as Markdown. +// An empty string is returned when the pager has no pages. +func Picker(p Pager) string { + if !p.HasPages() { + return "" + } + + var out strings.Builder + + if s := p.PrevPageURI(); s != "" { + out.WriteString("[«](" + s + ") | ") + } else { + out.WriteString("\\- | ") + } + + out.WriteString("page " + strconv.Itoa(p.Page()) + " of " + strconv.Itoa(p.PageCount())) + + if s := p.NextPageURI(); s != "" { + out.WriteString(" | [»](" + s + ")") + } else { + out.WriteString(" | \\-") + } + + return out.String() +} diff --git a/examples/gno.land/p/jeronimoalbi/pager/pager_options.gno b/examples/gno.land/p/jeronimoalbi/pager/pager_options.gno new file mode 100644 index 00000000000..3feb467682b --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/pager/pager_options.gno @@ -0,0 +1,33 @@ +package pager + +import "strings" + +const ( + DefaultPageSize = 50 + DefaultPageQueryParam = "page" +) + +// PagerOption configures the pager. +type PagerOption func(*Pager) + +// WithPageSize assigns a page size to a pager. +func WithPageSize(size int) PagerOption { + return func(p *Pager) { + if size < 1 { + p.pageSize = DefaultPageSize + } else { + p.pageSize = size + } + } +} + +// WithPageQueryParam assigns the name of the URL query param for the page value. +func WithPageQueryParam(name string) PagerOption { + return func(p *Pager) { + name = strings.TrimSpace(name) + if name == "" { + name = DefaultPageQueryParam + } + p.pageQueryParam = name + } +} diff --git a/examples/gno.land/p/jeronimoalbi/pager/pager_test.gno b/examples/gno.land/p/jeronimoalbi/pager/pager_test.gno new file mode 100644 index 00000000000..f498079cad3 --- /dev/null +++ b/examples/gno.land/p/jeronimoalbi/pager/pager_test.gno @@ -0,0 +1,204 @@ +package pager + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestPager(t *testing.T) { + cases := []struct { + name, uri, prevPath, nextPath, param string + offset, pageSize, page, pageCount int + hasPages bool + items []int + err error + }{ + { + name: "page 1", + uri: "gno.land/r/demo/test:foo/bar?page=1&foo=bar", + items: []int{1, 2, 3, 4, 5, 6}, + hasPages: true, + nextPath: "?foo=bar&page=2", + pageSize: 5, + page: 1, + pageCount: 2, + }, + { + name: "page 2", + uri: "gno.land/r/demo/test:foo/bar?page=2&foo=bar", + items: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + hasPages: true, + prevPath: "?foo=bar&page=1", + nextPath: "", + offset: 5, + pageSize: 5, + page: 2, + pageCount: 2, + }, + { + name: "custom query param", + uri: "gno.land/r/demo/test:foo/bar?current=2&foo=bar", + items: []int{1, 2, 3}, + param: "current", + hasPages: true, + prevPath: "?current=1&foo=bar", + nextPath: "", + offset: 2, + pageSize: 2, + page: 2, + pageCount: 2, + }, + { + name: "missing page", + uri: "gno.land/r/demo/test:foo/bar?page=3&foo=bar", + err: ErrInvalidPageNumber, + }, + { + name: "invalid page zero", + uri: "gno.land/r/demo/test:foo/bar?page=0", + err: ErrInvalidPageNumber, + }, + { + name: "invalid page number", + uri: "gno.land/r/demo/test:foo/bar?page=foo", + err: ErrInvalidPageNumber, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Act + p, err := New(tc.uri, len(tc.items), WithPageSize(tc.pageSize), WithPageQueryParam(tc.param)) + + // Assert + if tc.err != nil { + urequire.ErrorIs(t, err, tc.err, "expected an error") + return + } + + urequire.NoError(t, err, "expect no error") + uassert.Equal(t, len(tc.items), p.TotalItems(), "total items") + uassert.Equal(t, tc.page, p.Page(), "page number") + uassert.Equal(t, tc.pageCount, p.PageCount(), "number of pages") + uassert.Equal(t, tc.pageSize, p.PageSize(), "page size") + uassert.Equal(t, tc.prevPath, p.PrevPageURI(), "prev URL page") + uassert.Equal(t, tc.nextPath, p.NextPageURI(), "next URL page") + uassert.Equal(t, tc.hasPages, p.HasPages(), "has pages") + uassert.Equal(t, tc.offset, p.Offset(), "item offset") + }) + } +} + +func TestPagerIterate(t *testing.T) { + cases := []struct { + name, uri string + items, page []int + stop bool + }{ + { + name: "page 1", + uri: "gno.land/r/demo/test:foo/bar?page=1", + items: []int{1, 2, 3, 4, 5, 6, 7}, + page: []int{1, 2, 3}, + }, + { + name: "page 2", + uri: "gno.land/r/demo/test:foo/bar?page=2", + items: []int{1, 2, 3, 4, 5, 6, 7}, + page: []int{4, 5, 6}, + }, + { + name: "page 3", + uri: "gno.land/r/demo/test:foo/bar?page=3", + items: []int{1, 2, 3, 4, 5, 6, 7}, + page: []int{7}, + }, + { + name: "stop iteration", + uri: "gno.land/r/demo/test:foo/bar?page=1", + items: []int{1, 2, 3}, + stop: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Arrange + var ( + items []int + p = MustNew(tc.uri, len(tc.items), WithPageSize(3)) + ) + + // Act + stopped := p.Iterate(func(i int) bool { + if tc.stop { + return true + } + + items = append(items, tc.items[i]) + return false + }) + + // Assert + uassert.Equal(t, tc.stop, stopped) + urequire.Equal(t, len(tc.page), len(items), "expect iteration of the right number of items") + + for i, v := range items { + urequire.Equal(t, tc.page[i], v, "expect iterated items to match") + } + }) + } +} + +func TestPicker(t *testing.T) { + pageSize := 3 + cases := []struct { + name, uri, output string + totalItems int + }{ + { + name: "one page", + uri: "gno.land/r/demo/test:foo/bar?page=1", + totalItems: 3, + output: "", + }, + { + name: "two pages", + uri: "gno.land/r/demo/test:foo/bar?page=1", + totalItems: 4, + output: "\\- | page 1 of 2 | [»](?page=2)", + }, + { + name: "three pages", + uri: "gno.land/r/demo/test:foo/bar?page=1", + totalItems: 7, + output: "\\- | page 1 of 3 | [»](?page=2)", + }, + { + name: "three pages second page", + uri: "gno.land/r/demo/test:foo/bar?page=2", + totalItems: 7, + output: "[«](?page=1) | page 2 of 3 | [»](?page=3)", + }, + { + name: "three pages third page", + uri: "gno.land/r/demo/test:foo/bar?page=3", + totalItems: 7, + output: "[«](?page=2) | page 3 of 3 | \\-", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Arrange + p := MustNew(tc.uri, tc.totalItems, WithPageSize(pageSize)) + + // Act + output := Picker(p) + + // Assert + uassert.Equal(t, tc.output, output) + }) + } +} diff --git a/examples/gno.land/p/leon/pkgerr/err.gno b/examples/gno.land/p/leon/pkgerr/err.gno new file mode 100644 index 00000000000..d30e874b340 --- /dev/null +++ b/examples/gno.land/p/leon/pkgerr/err.gno @@ -0,0 +1,49 @@ +// Package pkgerr provides a custom error wrapper that prepends the realm link to your error +// This is useful for identifying the source package of the error. +// +// Usage: +// +// To wrap an error with realm and chain domain information, use the `New` function: +// +// err := pkgerr.New("my error message") // in gno.land/r/leon/example +// fmt.Println(err.Error()) // Output: "r/leon/example: my error message" +package pkgerr + +import ( + "errors" + "std" + "strings" + + "gno.land/p/demo/ufmt" +) + +// pkgErr is a custom error type that prepends the current +// realm link to the original error message. +type pkgErr struct { + originalErr error +} + +// New creates a new pkgErr with the given error. The returned error will include +// the current realm link in its message. +func New(msg string) error { + return &pkgErr{originalErr: errors.New(msg)} +} + +func Wrap(err error) error { + if err == nil { + return nil + } + + return &pkgErr{originalErr: err} +} + +func (e pkgErr) Unwrap() error { + return e.originalErr +} + +// Error implements the `error` interface for pkgErr. +func (e *pkgErr) Error() string { + return ufmt.Sprintf("%s: %s", + strings.TrimPrefix(std.CurrentRealm().PkgPath(), "gno.land/"), + e.originalErr) +} diff --git a/examples/gno.land/p/leon/pkgerr/err_test.gno b/examples/gno.land/p/leon/pkgerr/err_test.gno new file mode 100644 index 00000000000..f5b296edf99 --- /dev/null +++ b/examples/gno.land/p/leon/pkgerr/err_test.gno @@ -0,0 +1,84 @@ +package pkgerr + +import ( + "errors" + "std" + "strings" + "testing" +) + +const pkgPath = "gno.land/r/leon/test" + +var prefix = strings.TrimPrefix(pkgPath, "gno.land/") + +func TestNew(t *testing.T) { + err := New("my error message") + if err == nil { + t.Fatal("Expected an error, got nil") + } + + std.TestSetRealm(std.NewCodeRealm(pkgPath)) + + expected := prefix + ": my error message" + + if err.Error() != expected { + t.Errorf("Expected error message %q, got %q", expected, err.Error()) + } +} + +func TestWrap(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm(pkgPath)) + + originalErr := errors.New("original error") + wrappedErr := Wrap(originalErr) + if wrappedErr == nil { + t.Fatal("Expected an error, got nil") + } + + expected := prefix + ": original error" + if wrappedErr.Error() != expected { + t.Errorf("Expected error message %q, got %q", expected, wrappedErr.Error()) + } +} + +func TestUnwrap(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm(pkgPath)) + originalErr := errors.New("original error") + wrappedErr := Wrap(originalErr) + + unwrappedErr := wrappedErr.(*pkgErr).Unwrap() + if unwrappedErr != originalErr { + t.Errorf("Expected unwrapped error %v, got %v", originalErr, unwrappedErr) + } +} + +func TestErrorMethod(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm(pkgPath)) + originalErr := errors.New("original error") + pkgErr := &pkgErr{originalErr: originalErr} + + expected := prefix + ": original error" + if pkgErr.Error() != expected { + t.Errorf("Expected error message %q, got %q", expected, pkgErr.Error()) + } +} + +func TestWrapNilError(t *testing.T) { + err := Wrap(nil) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } +} + +func TestNewWithEmptyMessage(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm(pkgPath)) + err := New("") + if err == nil { + t.Fatal("Expected an error, got nil") + } + + expected := prefix + ": " + if err.Error() != expected { + t.Errorf("Expected error message %q, got %q", expected, err.Error()) + } +} diff --git a/examples/gno.land/p/leon/pkgerr/gno.mod b/examples/gno.land/p/leon/pkgerr/gno.mod new file mode 100644 index 00000000000..ebeb8e85a8f --- /dev/null +++ b/examples/gno.land/p/leon/pkgerr/gno.mod @@ -0,0 +1 @@ +module gno.land/p/leon/pkgerr diff --git a/examples/gno.land/p/mason/md/gno.mod b/examples/gno.land/p/mason/md/gno.mod new file mode 100644 index 00000000000..1f154210baf --- /dev/null +++ b/examples/gno.land/p/mason/md/gno.mod @@ -0,0 +1 @@ +module gno.land/p/mason/md diff --git a/examples/gno.land/p/mason/md/md.gno b/examples/gno.land/p/mason/md/md.gno new file mode 100644 index 00000000000..06e69b2c668 --- /dev/null +++ b/examples/gno.land/p/mason/md/md.gno @@ -0,0 +1,48 @@ +package md + +import ( + "strings" +) + +type MD struct { + elements []string +} + +func New() *MD { + return &MD{elements: []string{}} +} + +func (m *MD) H1(text string) { + m.elements = append(m.elements, "# "+text) +} + +func (m *MD) H3(text string) { + m.elements = append(m.elements, "### "+text) +} + +func (m *MD) P(text string) { + m.elements = append(m.elements, text) +} + +func (m *MD) Code(text string) { + m.elements = append(m.elements, " ```\n"+text+"\n```\n") +} + +func (m *MD) Im(path string, caption string) { + m.elements = append(m.elements, "!["+caption+"]("+path+" \""+caption+"\")") +} + +func (m *MD) Bullet(point string) { + m.elements = append(m.elements, "- "+point) +} + +func Link(text, url string, title ...string) string { + if len(title) > 0 && title[0] != "" { + return "[" + text + "](" + url + " \"" + title[0] + "\")" + } + return "[" + text + "](" + url + ")" +} + +func (m *MD) Render() string { + return strings.Join(m.elements, "\n\n") +} diff --git a/examples/gno.land/p/moul/addrset/addrset.gno b/examples/gno.land/p/moul/addrset/addrset.gno index 0bb8165f9fe..36380e8409c 100644 --- a/examples/gno.land/p/moul/addrset/addrset.gno +++ b/examples/gno.land/p/moul/addrset/addrset.gno @@ -81,7 +81,7 @@ func (s *Set) Size() int { // IterateByOffset walks through addresses starting at the given offset. // The callback should return true to stop iteration. func (s *Set) IterateByOffset(offset int, count int, cb func(addr std.Address) bool) { - s.tree.IterateByOffset(offset, count, func(key string, _ interface{}) bool { + s.tree.IterateByOffset(offset, count, func(key string, _ any) bool { return cb(std.Address(key)) }) } @@ -89,7 +89,7 @@ func (s *Set) IterateByOffset(offset int, count int, cb func(addr std.Address) b // ReverseIterateByOffset walks through addresses in reverse order starting at the given offset. // The callback should return true to stop iteration. func (s *Set) ReverseIterateByOffset(offset int, count int, cb func(addr std.Address) bool) { - s.tree.ReverseIterateByOffset(offset, count, func(key string, _ interface{}) bool { + s.tree.ReverseIterateByOffset(offset, count, func(key string, _ any) bool { return cb(std.Address(key)) }) } diff --git a/examples/gno.land/p/moul/authz/authz.gno b/examples/gno.land/p/moul/authz/authz.gno new file mode 100644 index 00000000000..e3c7d3f5f54 --- /dev/null +++ b/examples/gno.land/p/moul/authz/authz.gno @@ -0,0 +1,261 @@ +// Package authz provides flexible authorization control for privileged actions. +// +// # Authorization Strategies +// +// The package supports multiple authorization strategies: +// - Member-based: Single user or team of users +// - Contract-based: Async authorization (e.g., via DAO) +// - Auto-accept: Allow all actions +// - Drop: Deny all actions +// +// Core Components +// +// - Authority interface: Base interface implemented by all authorities +// - Authorizer: Main wrapper object for authority management +// - MemberAuthority: Manages authorized addresses +// - ContractAuthority: Delegates to another contract +// - AutoAcceptAuthority: Accepts all actions +// - DroppedAuthority: Denies all actions +// +// Quick Start +// +// // Initialize with contract deployer as authority +// var auth = authz.New() +// +// // Create functions that require authorization +// func UpdateConfig(newValue string) error { +// return auth.Do("update_config", func() error { +// config = newValue +// return nil +// }) +// } +// +// See example_test.gno for more usage examples. +package authz + +import ( + "errors" + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/avl/rotree" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/addrset" + "gno.land/p/moul/once" +) + +// Authorizer is the main wrapper object that handles authority management +type Authorizer struct { + current Authority +} + +// Authority represents an entity that can authorize privileged actions +type Authority interface { + // Authorize executes a privileged action if the caller is authorized + // Additional args can be provided for context (e.g., for proposal creation) + Authorize(title string, action PrivilegedAction, args ...any) error + + // String returns a human-readable description of the authority + String() string +} + +// PrivilegedAction defines a function that performs a privileged action. +type PrivilegedAction func() error + +// PrivilegedActionHandler is called by contract-based authorities to handle +// privileged actions. +type PrivilegedActionHandler func(title string, action PrivilegedAction) error + +// New creates a new Authorizer with the current realm's address as authority +func New() *Authorizer { + return &Authorizer{ + current: NewMemberAuthority(std.PreviousRealm().Address()), + } +} + +// NewWithAuthority creates a new Authorizer with a specific authority +func NewWithAuthority(authority Authority) *Authorizer { + return &Authorizer{ + current: authority, + } +} + +// Current returns the current authority implementation +func (a *Authorizer) Current() Authority { + return a.current +} + +// Transfer changes the current authority after validation +func (a *Authorizer) Transfer(newAuthority Authority) error { + // Ask current authority to validate the transfer + return a.current.Authorize("transfer_authority", func() error { + a.current = newAuthority + return nil + }) +} + +// Do executes a privileged action through the current authority +func (a *Authorizer) Do(title string, action PrivilegedAction, args ...any) error { + return a.current.Authorize(title, action, args...) +} + +// String returns a string representation of the current authority +func (a *Authorizer) String() string { + return a.current.String() +} + +// MemberAuthority is the default implementation using addrset for member management +type MemberAuthority struct { + members addrset.Set +} + +func NewMemberAuthority(members ...std.Address) *MemberAuthority { + auth := &MemberAuthority{} + for _, addr := range members { + auth.members.Add(addr) + } + return auth +} + +func (a *MemberAuthority) Authorize(title string, action PrivilegedAction, args ...any) error { + caller := std.PreviousRealm().Address() + if !a.members.Has(caller) { + return errors.New("unauthorized") + } + + if err := action(); err != nil { + return err + } + return nil +} + +func (a *MemberAuthority) String() string { + return ufmt.Sprintf("member_authority[size=%d]", a.members.Size()) +} + +// AddMember adds a new member to the authority +func (a *MemberAuthority) AddMember(addr std.Address) error { + return a.Authorize("add_member", func() error { + a.members.Add(addr) + return nil + }) +} + +// RemoveMember removes a member from the authority +func (a *MemberAuthority) RemoveMember(addr std.Address) error { + return a.Authorize("remove_member", func() error { + a.members.Remove(addr) + return nil + }) +} + +// Tree returns a read-only view of the members tree +func (a *MemberAuthority) Tree() *rotree.ReadOnlyTree { + tree := a.members.Tree().(*avl.Tree) + return rotree.Wrap(tree, nil) +} + +// Has checks if the given address is a member of the authority +func (a *MemberAuthority) Has(addr std.Address) bool { + return a.members.Has(addr) +} + +// ContractAuthority implements async contract-based authority +type ContractAuthority struct { + contractPath string + contractAddr std.Address + contractHandler PrivilegedActionHandler + proposer Authority // controls who can create proposals + executionOnce once.Once +} + +func NewContractAuthority(path string, handler PrivilegedActionHandler) *ContractAuthority { + return &ContractAuthority{ + contractPath: path, + contractAddr: std.DerivePkgAddr(path), + contractHandler: handler, + proposer: NewAutoAcceptAuthority(), // default: anyone can propose + executionOnce: once.Once{}, // initialize execution once + } +} + +// NewRestrictedContractAuthority creates a new contract authority with a proposer restriction +func NewRestrictedContractAuthority(path string, handler PrivilegedActionHandler, proposer Authority) Authority { + if path == "" { + panic("contract path cannot be empty") + } + if handler == nil { + panic("contract handler cannot be nil") + } + if proposer == nil { + panic("proposer cannot be nil") + } + return &ContractAuthority{ + contractPath: path, + contractAddr: std.DerivePkgAddr(path), + contractHandler: handler, + proposer: proposer, + executionOnce: once.Once{}, + } +} + +func (a *ContractAuthority) Authorize(title string, action PrivilegedAction, args ...any) error { + if a.contractHandler == nil { + return errors.New("contract handler is not set") + } + + // Wrap the action to ensure it can only be executed by the contract + wrappedAction := func() error { + caller := std.PreviousRealm().Address() + if caller != a.contractAddr { + return errors.New("action can only be executed by the contract") + } + return a.executionOnce.DoErr(func() error { + return action() + }) + } + + // Use the proposer authority to control who can create proposals + return a.proposer.Authorize(title+"_proposal", func() error { + if err := a.contractHandler(title, wrappedAction); err != nil { + return err + } + return nil + }, args...) +} + +func (a *ContractAuthority) String() string { + return ufmt.Sprintf("contract_authority[contract=%s]", a.contractPath) +} + +// AutoAcceptAuthority implements an authority that accepts all actions +// AutoAcceptAuthority is a simple authority that automatically accepts all actions. +// It can be used as a proposer authority to allow anyone to create proposals. +type AutoAcceptAuthority struct{} + +func NewAutoAcceptAuthority() *AutoAcceptAuthority { + return &AutoAcceptAuthority{} +} + +func (a *AutoAcceptAuthority) Authorize(title string, action PrivilegedAction, args ...any) error { + return action() +} + +func (a *AutoAcceptAuthority) String() string { + return "auto_accept_authority" +} + +// droppedAuthority implements an authority that denies all actions +type droppedAuthority struct{} + +func NewDroppedAuthority() Authority { + return &droppedAuthority{} +} + +func (a *droppedAuthority) Authorize(title string, action PrivilegedAction, args ...any) error { + return errors.New("dropped authority: all actions are denied") +} + +func (a *droppedAuthority) String() string { + return "dropped_authority" +} diff --git a/examples/gno.land/p/moul/authz/authz_test.gno b/examples/gno.land/p/moul/authz/authz_test.gno new file mode 100644 index 00000000000..e84091cf171 --- /dev/null +++ b/examples/gno.land/p/moul/authz/authz_test.gno @@ -0,0 +1,384 @@ +package authz + +import ( + "errors" + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" +) + +func TestNew(t *testing.T) { + testAddr := testutils.TestAddress("alice") + std.TestSetOriginCaller(testAddr) + + auth := New() + + // Check that the current authority is a MemberAuthority + memberAuth, ok := auth.Current().(*MemberAuthority) + uassert.True(t, ok, "expected MemberAuthority") + + // Check that the caller is a member + uassert.True(t, memberAuth.Has(testAddr), "caller should be a member") + + // Check string representation + expected := ufmt.Sprintf("member_authority[size=%d]", 1) + uassert.Equal(t, expected, auth.String()) +} + +func TestNewWithAuthority(t *testing.T) { + testAddr := testutils.TestAddress("alice") + memberAuth := NewMemberAuthority(testAddr) + + auth := NewWithAuthority(memberAuth) + + // Check that the current authority is the one we provided + uassert.True(t, auth.Current() == memberAuth, "expected provided authority") +} + +func TestAuthorizerAuthorize(t *testing.T) { + testAddr := testutils.TestAddress("alice") + std.TestSetOriginCaller(testAddr) + + auth := New() + + // Test successful action with args + executed := false + args := []any{"test_arg", 123} + err := auth.Do("test_action", func() error { + executed = true + return nil + }, args...) + + uassert.True(t, err == nil, "expected no error") + uassert.True(t, executed, "action should have been executed") + + // Test unauthorized action with args + std.TestSetOriginCaller(testutils.TestAddress("bob")) + + executed = false + err = auth.Do("test_action", func() error { + executed = true + return nil + }, "unauthorized_arg") + + uassert.True(t, err != nil, "expected error") + uassert.False(t, executed, "action should not have been executed") + + // Test action returning error + std.TestSetOriginCaller(testAddr) + expectedErr := errors.New("test error") + + err = auth.Do("test_action", func() error { + return expectedErr + }) + + uassert.True(t, err == expectedErr, "expected specific error") +} + +func TestAuthorizerTransfer(t *testing.T) { + testAddr := testutils.TestAddress("alice") + std.TestSetOriginCaller(testAddr) + + auth := New() + + // Test transfer to new member authority + newAddr := testutils.TestAddress("bob") + newAuth := NewMemberAuthority(newAddr) + + err := auth.Transfer(newAuth) + uassert.True(t, err == nil, "expected no error") + uassert.True(t, auth.Current() == newAuth, "expected new authority") + + // Test unauthorized transfer + std.TestSetOriginCaller(testutils.TestAddress("carol")) + + err = auth.Transfer(NewMemberAuthority(testAddr)) + uassert.True(t, err != nil, "expected error") + + // Test transfer to contract authority + std.TestSetOriginCaller(newAddr) + + contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error { + return action() + }) + + err = auth.Transfer(contractAuth) + uassert.True(t, err == nil, "expected no error") + uassert.True(t, auth.Current() == contractAuth, "expected contract authority") +} + +func TestAuthorizerTransferChain(t *testing.T) { + testAddr := testutils.TestAddress("alice") + std.TestSetOriginCaller(testAddr) + + // Create a chain of transfers + auth := New() + + // First transfer to a new member authority + newAddr := testutils.TestAddress("bob") + memberAuth := NewMemberAuthority(newAddr) + + err := auth.Transfer(memberAuth) + uassert.True(t, err == nil, "unexpected error in first transfer") + + // Then transfer to a contract authority + contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error { + return action() + }) + + std.TestSetOriginCaller(newAddr) + err = auth.Transfer(contractAuth) + uassert.True(t, err == nil, "unexpected error in second transfer") + + // Finally transfer to an auto-accept authority + autoAuth := NewAutoAcceptAuthority() + + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/test")) + err = auth.Transfer(autoAuth) + uassert.True(t, err == nil, "unexpected error in final transfer") + uassert.True(t, auth.Current() == autoAuth, "expected auto-accept authority") +} + +func TestAuthorizerWithDroppedAuthority(t *testing.T) { + testAddr := testutils.TestAddress("alice") + std.TestSetOriginCaller(testAddr) + + auth := New() + + // Transfer to dropped authority + err := auth.Transfer(NewDroppedAuthority()) + uassert.True(t, err == nil, "expected no error") + + // Try to execute action + err = auth.Do("test_action", func() error { + return nil + }) + uassert.True(t, err != nil, "expected error from dropped authority") + + // Try to transfer again + err = auth.Transfer(NewMemberAuthority(testAddr)) + uassert.True(t, err != nil, "expected error when transferring from dropped authority") +} + +func TestContractAuthorityExecutionOnce(t *testing.T) { + attempts := 0 + executed := 0 + var capturedArgs []any + + contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error { + // Try to execute the action twice in the same handler + if err := action(); err != nil { + return err + } + attempts++ + + // Second execution should fail + if err := action(); err != nil { + return err + } + attempts++ + return nil + }) + + // Set caller to contract address + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/test")) + + testArgs := []any{"proposal_id", 42, "metadata", map[string]string{"key": "value"}} + err := contractAuth.Authorize("test_action", func() error { + executed++ + return nil + }, testArgs...) + + uassert.True(t, err == nil, "handler execution should succeed") + uassert.True(t, attempts == 2, "handler should have attempted execution twice") + uassert.True(t, executed == 1, "handler should have executed once") +} + +func TestContractAuthorityWithProposer(t *testing.T) { + testAddr := testutils.TestAddress("alice") + memberAuth := NewMemberAuthority(testAddr) + + handlerCalled := false + actionExecuted := false + + contractAuth := NewRestrictedContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error { + handlerCalled = true + // Set caller to contract address before executing action + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/test")) + return action() + }, memberAuth) + + // Test authorized member + std.TestSetOriginCaller(testAddr) + testArgs := []any{"proposal_metadata", "test value"} + err := contractAuth.Authorize("test_action", func() error { + actionExecuted = true + return nil + }, testArgs...) + + uassert.True(t, err == nil, "authorized member should be able to propose") + uassert.True(t, handlerCalled, "contract handler should be called") + uassert.True(t, actionExecuted, "action should be executed") + + // Reset flags for unauthorized test + handlerCalled = false + actionExecuted = false + + // Test unauthorized proposer + std.TestSetOriginCaller(testutils.TestAddress("bob")) + err = contractAuth.Authorize("test_action", func() error { + actionExecuted = true + return nil + }, testArgs...) + + uassert.True(t, err != nil, "unauthorized member should not be able to propose") + uassert.False(t, handlerCalled, "contract handler should not be called for unauthorized proposer") + uassert.False(t, actionExecuted, "action should not be executed for unauthorized proposer") +} + +func TestAutoAcceptAuthority(t *testing.T) { + auth := NewAutoAcceptAuthority() + + // Test that any action is authorized + executed := false + err := auth.Authorize("test_action", func() error { + executed = true + return nil + }) + + uassert.True(t, err == nil, "auto-accept should not return error") + uassert.True(t, executed, "action should have been executed") + + // Test with different caller + std.TestSetOriginCaller(testutils.TestAddress("random")) + executed = false + err = auth.Authorize("test_action", func() error { + executed = true + return nil + }) + + uassert.True(t, err == nil, "auto-accept should not care about caller") + uassert.True(t, executed, "action should have been executed") +} + +func TestAutoAcceptAuthorityWithArgs(t *testing.T) { + auth := NewAutoAcceptAuthority() + + // Test that any action is authorized with args + executed := false + testArgs := []any{"arg1", 42, "arg3"} + err := auth.Authorize("test_action", func() error { + executed = true + return nil + }, testArgs...) + + uassert.True(t, err == nil, "auto-accept should not return error") + uassert.True(t, executed, "action should have been executed") +} + +func TestMemberAuthorityMultipleMembers(t *testing.T) { + alice := testutils.TestAddress("alice") + bob := testutils.TestAddress("bob") + carol := testutils.TestAddress("carol") + + // Create authority with multiple members + auth := NewMemberAuthority(alice, bob) + + // Test that both members can execute actions + for _, member := range []std.Address{alice, bob} { + std.TestSetOriginCaller(member) + err := auth.Authorize("test_action", func() error { + return nil + }) + uassert.True(t, err == nil, "member should be authorized") + } + + // Test that non-member cannot execute + std.TestSetOriginCaller(carol) + err := auth.Authorize("test_action", func() error { + return nil + }) + uassert.True(t, err != nil, "non-member should not be authorized") + + // Test Tree() functionality + tree := auth.Tree() + uassert.True(t, tree.Size() == 2, "tree should have 2 members") + + // Verify both members are in the tree + found := make(map[std.Address]bool) + tree.Iterate("", "", func(key string, _ any) bool { + found[std.Address(key)] = true + return false + }) + uassert.True(t, found[alice], "alice should be in the tree") + uassert.True(t, found[bob], "bob should be in the tree") + uassert.False(t, found[carol], "carol should not be in the tree") + + // Test read-only nature of the tree + defer func() { + r := recover() + uassert.True(t, r != nil, "modifying read-only tree should panic") + }() + tree.Set(string(carol), nil) // This should panic +} + +func TestAuthorizerCurrentNeverNil(t *testing.T) { + auth := New() + + // Current should never be nil after initialization + uassert.True(t, auth.Current() != nil, "current authority should not be nil") + + // Current should not be nil after transfer + err := auth.Transfer(NewAutoAcceptAuthority()) + uassert.True(t, err == nil, "transfer should succeed") + uassert.True(t, auth.Current() != nil, "current authority should not be nil after transfer") +} + +func TestAuthorizerString(t *testing.T) { + auth := New() + + // Test initial string representation + str := auth.String() + uassert.True(t, str != "", "string representation should not be empty") + + // Test string after transfer + autoAuth := NewAutoAcceptAuthority() + err := auth.Transfer(autoAuth) + uassert.True(t, err == nil, "transfer should succeed") + + newStr := auth.String() + uassert.True(t, newStr != "", "string representation should not be empty") + uassert.True(t, newStr != str, "string should change after transfer") +} + +func TestContractAuthorityValidation(t *testing.T) { + // Test empty path + auth := NewContractAuthority("", nil) + std.TestSetOriginCaller(std.DerivePkgAddr("")) + err := auth.Authorize("test", func() error { + return nil + }) + uassert.True(t, err != nil, "empty path authority should fail to authorize") + + // Test nil handler + auth = NewContractAuthority("gno.land/r/test", nil) + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/test")) + err = auth.Authorize("test", func() error { + return nil + }) + uassert.True(t, err != nil, "nil handler authority should fail to authorize") + + // Test valid configuration + handler := func(title string, action PrivilegedAction) error { + return nil + } + contractAuth := NewContractAuthority("gno.land/r/test", handler) + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/test")) + err = contractAuth.Authorize("test", func() error { + return nil + }) + uassert.True(t, err == nil, "valid contract authority should authorize successfully") +} diff --git a/examples/gno.land/p/moul/authz/example_test.gno b/examples/gno.land/p/moul/authz/example_test.gno new file mode 100644 index 00000000000..a283204dfc2 --- /dev/null +++ b/examples/gno.land/p/moul/authz/example_test.gno @@ -0,0 +1,88 @@ +package authz + +import ( + "std" +) + +// Example_basic demonstrates initializing and using a basic member authority +func Example_basic() { + // Initialize with contract deployer as authority + auth := New() + + // Use the authority to perform a privileged action + auth.Do("update_config", func() error { + // config = newValue + return nil + }) +} + +// Example_addingMembers demonstrates how to add new members to a member authority +func Example_addingMembers() { + // Initialize with contract deployer as authority + auth := New() + + // Add a new member to the authority + memberAuth := auth.Current().(*MemberAuthority) + memberAuth.AddMember(std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")) +} + +// Example_contractAuthority demonstrates using a contract-based authority +func Example_contractAuthority() { + // Initialize with contract authority (e.g., DAO) + auth := NewWithAuthority( + NewContractAuthority( + "gno.land/r/demo/dao", + mockDAOHandler, // defined elsewhere for example + ), + ) + + // Privileged actions will be handled by the contract + auth.Do("update_params", func() error { + // Executes after DAO approval + return nil + }) +} + +// Example_restrictedContractAuthority demonstrates a contract authority with member-only proposals +func Example_restrictedContractAuthority() { + // Initialize member authority for proposers + proposerAuth := NewMemberAuthority( + std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), // admin1 + std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj"), // admin2 + ) + + // Create contract authority with restricted proposers + auth := NewWithAuthority( + NewRestrictedContractAuthority( + "gno.land/r/demo/dao", + mockDAOHandler, + proposerAuth, + ), + ) + + // Only members can propose, and contract must approve + auth.Do("update_params", func() error { + // Executes after: + // 1. Proposer initiates + // 2. DAO approves + return nil + }) +} + +// Example_switchingAuthority demonstrates switching from member to contract authority +func Example_switchingAuthority() { + // Start with member authority (deployer) + auth := New() + + // Create and switch to contract authority + daoAuthority := NewContractAuthority( + "gno.land/r/demo/dao", + mockDAOHandler, + ) + auth.Transfer(daoAuthority) +} + +// Mock handler for examples +func mockDAOHandler(title string, action PrivilegedAction) error { + return action() +} diff --git a/examples/gno.land/p/moul/authz/gno.mod b/examples/gno.land/p/moul/authz/gno.mod new file mode 100644 index 00000000000..2ae057d875b --- /dev/null +++ b/examples/gno.land/p/moul/authz/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/authz diff --git a/examples/gno.land/p/moul/collection/collection.gno b/examples/gno.land/p/moul/collection/collection.gno index f6d26e6a3ee..49b73c9f3a0 100644 --- a/examples/gno.land/p/moul/collection/collection.gno +++ b/examples/gno.land/p/moul/collection/collection.gno @@ -18,24 +18,24 @@ // c := collection.New() // // // Add indexes with different options -// c.AddIndex("name", func(v interface{}) string { +// c.AddIndex("name", func(v any) string { // return v.(*User).Name // }, UniqueIndex) // -// c.AddIndex("email", func(v interface{}) string { +// c.AddIndex("email", func(v any) string { // return v.(*User).Email // }, UniqueIndex|CaseInsensitiveIndex) // -// c.AddIndex("age", func(v interface{}) string { +// c.AddIndex("age", func(v any) string { // return strconv.Itoa(v.(*User).Age) // }, DefaultIndex) // Non-unique index // -// c.AddIndex("username", func(v interface{}) string { +// c.AddIndex("username", func(v any) string { // return v.(*User).Username // }, UniqueIndex|SparseIndex) // Allow empty usernames // // // For tags, we index all tags for the user -// c.AddIndex("tag", func(v interface{}) []string { +// c.AddIndex("tag", func(v any) []string { // return v.(*User).Tags // }, DefaultIndex) // Non-unique to allow multiple users with same tag // @@ -121,7 +121,7 @@ const ( // // The backing tree stores either a single ID or []string for multiple IDs per key. type Index struct { - fn interface{} + fn any options IndexOption tree avl.ITree } @@ -132,7 +132,7 @@ type Index struct { // - name: the unique name of the index (e.g., "tags") // - indexFn: a function that extracts either a string or []string from an object // - options: bit flags for index configuration (e.g., UniqueIndex) -func (c *Collection) AddIndex(name string, indexFn interface{}, options IndexOption) { +func (c *Collection) AddIndex(name string, indexFn any, options IndexOption) { if name == IDIndex { panic("_id is a reserved index name") } @@ -202,17 +202,17 @@ func (idx *Index) remove(key string, idStr string) { } // generateKeys extracts one or more keys from an object for a given index. -func generateKeys(idx *Index, obj interface{}) ([]string, bool) { +func generateKeys(idx *Index, obj any) ([]string, bool) { if obj == nil { return nil, false } switch fnTyped := idx.fn.(type) { - case func(interface{}) string: + case func(any) string: // Single-value index key := fnTyped(obj) return []string{key}, true - case func(interface{}) []string: + case func(any) []string: // Multi-value index keys := fnTyped(obj) return keys, true @@ -227,7 +227,7 @@ func generateKeys(idx *Index, obj interface{}) ([]string, bool) { // - The object is nil // - A uniqueness constraint would be violated // - Index generation fails for any index -func (c *Collection) Set(obj interface{}) uint64 { +func (c *Collection) Set(obj any) uint64 { if obj == nil { return 0 } @@ -368,7 +368,7 @@ func (c *Collection) Delete(id uint64) bool { // - Index generation fails for any index // // If the update fails, the collection remains unchanged. -func (c *Collection) Update(id uint64, obj interface{}) bool { +func (c *Collection) Update(id uint64, obj any) bool { if obj == nil { return false } diff --git a/examples/gno.land/p/moul/collection/collection_test.gno b/examples/gno.land/p/moul/collection/collection_test.gno index 3e03d222ce8..ea2e493df3f 100644 --- a/examples/gno.land/p/moul/collection/collection_test.gno +++ b/examples/gno.land/p/moul/collection/collection_test.gno @@ -43,10 +43,10 @@ func TestBasicOperations(t *testing.T) { c := New() // Add indexes - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex) - c.AddIndex("age", func(v interface{}) string { + c.AddIndex("age", func(v any) string { return strconv.Itoa(v.(*Person).Age) }, DefaultIndex) @@ -92,7 +92,7 @@ func TestUniqueConstraints(t *testing.T) { { name: "Same age (non-unique index)", setup: func(c *Collection) uint64 { - c.AddIndex("age", func(v interface{}) string { + c.AddIndex("age", func(v any) string { return strconv.Itoa(v.(*Person).Age) }, DefaultIndex) c.Set(&Person{Name: "Alice", Age: 30}) @@ -105,7 +105,7 @@ func TestUniqueConstraints(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := New() - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex) @@ -119,10 +119,10 @@ func TestUniqueConstraints(t *testing.T) { func TestUpdates(t *testing.T) { c := New() - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex) - c.AddIndex("username", func(v interface{}) string { + c.AddIndex("username", func(v any) string { return strings.ToLower(v.(*Person).Username) }, UniqueIndex|CaseInsensitiveIndex) @@ -171,7 +171,7 @@ func TestUpdates(t *testing.T) { func TestDelete(t *testing.T) { c := New() - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex) @@ -212,7 +212,7 @@ func TestDelete(t *testing.T) { func TestEdgeCases(t *testing.T) { c := New() - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex) @@ -292,12 +292,12 @@ func TestIndexTypes(t *testing.T) { c := New() // Test different types of indexes - c.AddIndex("composite", func(v interface{}) string { + c.AddIndex("composite", func(v any) string { p := v.(*Person) return p.Name + ":" + strconv.Itoa(p.Age) }, UniqueIndex) - c.AddIndex("case_insensitive", func(v interface{}) string { + c.AddIndex("case_insensitive", func(v any) string { return strings.ToLower(v.(*Person).Username) }, UniqueIndex|CaseInsensitiveIndex) @@ -326,7 +326,7 @@ func TestIndexOptions(t *testing.T) { { name: "Unique case-sensitive index", setup: func(c *Collection) uint64 { - c.AddIndex("username", func(v interface{}) string { + c.AddIndex("username", func(v any) string { return v.(*Person).Username }, UniqueIndex) @@ -338,7 +338,7 @@ func TestIndexOptions(t *testing.T) { { name: "Unique case-insensitive index", setup: func(c *Collection) uint64 { - c.AddIndex("email", func(v interface{}) string { + c.AddIndex("email", func(v any) string { return v.(*Person).Email }, UniqueIndex|CaseInsensitiveIndex) @@ -350,7 +350,7 @@ func TestIndexOptions(t *testing.T) { { name: "Default index", setup: func(c *Collection) uint64 { - c.AddIndex("age", func(v interface{}) string { + c.AddIndex("age", func(v any) string { return strconv.Itoa(v.(*Person).Age) }, DefaultIndex) @@ -368,7 +368,7 @@ func TestIndexOptions(t *testing.T) { { name: "Multiple options", setup: func(c *Collection) uint64 { - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex|CaseInsensitiveIndex|SparseIndex) @@ -392,7 +392,7 @@ func TestIndexOptions(t *testing.T) { func TestConcurrentOperations(t *testing.T) { c := New() - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex) @@ -408,7 +408,7 @@ func TestConcurrentOperations(t *testing.T) { func TestSparseIndexBehavior(t *testing.T) { c := New() - c.AddIndex("optional_field", func(v interface{}) string { + c.AddIndex("optional_field", func(v any) string { return v.(*Person).Username }, SparseIndex) @@ -446,7 +446,7 @@ func TestSparseIndexBehavior(t *testing.T) { func TestIndexKeyGeneration(t *testing.T) { c := New() - c.AddIndex("composite", func(v interface{}) string { + c.AddIndex("composite", func(v any) string { p := v.(*Person) return p.Name + ":" + strconv.Itoa(p.Age) }, UniqueIndex) @@ -485,7 +485,7 @@ func TestIndexKeyGeneration(t *testing.T) { func TestGetIndex(t *testing.T) { c := New() - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex) @@ -529,14 +529,14 @@ func TestAddIndexPanic(t *testing.T) { } }() - c.AddIndex(IDIndex, func(v interface{}) string { + c.AddIndex(IDIndex, func(v any) string { return "" }, DefaultIndex) } func TestCaseInsensitiveOperations(t *testing.T) { c := New() - c.AddIndex("email", func(v interface{}) string { + c.AddIndex("email", func(v any) string { return v.(*Person).Email }, UniqueIndex|CaseInsensitiveIndex) @@ -604,13 +604,13 @@ func TestGetInvalidID(t *testing.T) { func TestGetAll(t *testing.T) { c := New() - c.AddIndex("tags", func(v interface{}) []string { + c.AddIndex("tags", func(v any) []string { return v.(*Person).Tags }, DefaultIndex) - c.AddIndex("age", func(v interface{}) string { + c.AddIndex("age", func(v any) string { return strconv.Itoa(v.(*Person).Age) }, DefaultIndex) - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex) @@ -704,7 +704,7 @@ func TestIndexOperations(t *testing.T) { { name: "Basic set and get", setup: func(c *Collection) (uint64, error) { - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex) return c.Set(&Person{Name: "Alice", Age: 30}), nil @@ -724,7 +724,7 @@ func TestIndexOperations(t *testing.T) { { name: "Composite index", setup: func(c *Collection) (uint64, error) { - c.AddIndex("composite", func(v interface{}) string { + c.AddIndex("composite", func(v any) string { p := v.(*Person) return p.Name + ":" + strconv.Itoa(p.Age) }, UniqueIndex) @@ -760,7 +760,7 @@ func TestIndexOperations(t *testing.T) { func TestMultiValueIndexes(t *testing.T) { c := New() - c.AddIndex("tags", func(v interface{}) []string { + c.AddIndex("tags", func(v any) []string { return v.(*Person).Tags }, DefaultIndex) @@ -812,7 +812,7 @@ func TestMultiValueIndexes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := New() - c.AddIndex("tags", func(v interface{}) []string { + c.AddIndex("tags", func(v any) []string { return v.(*Person).Tags }, DefaultIndex) @@ -838,10 +838,10 @@ func TestMultiValueIndexes(t *testing.T) { func TestGetOperations(t *testing.T) { c := New() - c.AddIndex("name", func(v interface{}) string { + c.AddIndex("name", func(v any) string { return v.(*Person).Name }, UniqueIndex) - c.AddIndex("age", func(v interface{}) string { + c.AddIndex("age", func(v any) string { return strconv.Itoa(v.(*Person).Age) }, DefaultIndex) diff --git a/examples/gno.land/p/moul/collection/entry.gno b/examples/gno.land/p/moul/collection/entry.gno index 8daa893b61d..5af9a34e69e 100644 --- a/examples/gno.land/p/moul/collection/entry.gno +++ b/examples/gno.land/p/moul/collection/entry.gno @@ -5,7 +5,7 @@ import "gno.land/p/demo/ufmt" // Entry represents a single object in the collection with its ID type Entry struct { ID string - Obj interface{} + Obj any } // String returns a string representation of the Entry @@ -22,7 +22,7 @@ type EntryIterator struct { indexName string key string currentID string - currentObj interface{} + currentObj any err error closed bool diff --git a/examples/gno.land/p/moul/cow/gno.mod b/examples/gno.land/p/moul/cow/gno.mod new file mode 100644 index 00000000000..e5dec0bc5b4 --- /dev/null +++ b/examples/gno.land/p/moul/cow/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/cow diff --git a/examples/gno.land/p/moul/cow/node.gno b/examples/gno.land/p/moul/cow/node.gno new file mode 100644 index 00000000000..9e0a12a2283 --- /dev/null +++ b/examples/gno.land/p/moul/cow/node.gno @@ -0,0 +1,518 @@ +package cow + +//---------------------------------------- +// Node + +// Node represents a node in an AVL tree. +type Node struct { + key string // key is the unique identifier for the node. + value any // value is the data stored in the node. + height int8 // height is the height of the node in the tree. + size int // size is the number of nodes in the subtree rooted at this node. + leftNode *Node // leftNode is the left child of the node. + rightNode *Node // rightNode is the right child of the node. +} + +// NewNode creates a new node with the given key and value. +func NewNode(key string, value any) *Node { + return &Node{ + key: key, + value: value, + height: 0, + size: 1, + } +} + +// Size returns the size of the subtree rooted at the node. +func (node *Node) Size() int { + if node == nil { + return 0 + } + return node.size +} + +// IsLeaf checks if the node is a leaf node (has no children). +func (node *Node) IsLeaf() bool { + return node.height == 0 +} + +// Key returns the key of the node. +func (node *Node) Key() string { + return node.key +} + +// Value returns the value of the node. +func (node *Node) Value() any { + return node.value +} + +// _copy creates a copy of the node (excluding value). +func (node *Node) _copy() *Node { + if node.height == 0 { + panic("Why are you copying a value node?") + } + return &Node{ + key: node.key, + height: node.height, + size: node.size, + leftNode: node.leftNode, + rightNode: node.rightNode, + } +} + +// Has checks if a node with the given key exists in the subtree rooted at the node. +func (node *Node) Has(key string) (has bool) { + if node == nil { + return false + } + if node.key == key { + return true + } + if node.height == 0 { + return false + } + if key < node.key { + return node.getLeftNode().Has(key) + } + return node.getRightNode().Has(key) +} + +// Get searches for a node with the given key in the subtree rooted at the node +// and returns its index, value, and whether it exists. +func (node *Node) Get(key string) (index int, value any, exists bool) { + if node == nil { + return 0, nil, false + } + + if node.height == 0 { + if node.key == key { + return 0, node.value, true + } + if node.key < key { + return 1, nil, false + } + return 0, nil, false + } + + if key < node.key { + return node.getLeftNode().Get(key) + } + + rightNode := node.getRightNode() + index, value, exists = rightNode.Get(key) + index += node.size - rightNode.size + return index, value, exists +} + +// GetByIndex retrieves the key-value pair of the node at the given index +// in the subtree rooted at the node. +func (node *Node) GetByIndex(index int) (key string, value any) { + if node.height == 0 { + if index == 0 { + return node.key, node.value + } + panic("GetByIndex asked for invalid index") + } + // TODO: could improve this by storing the sizes + leftNode := node.getLeftNode() + if index < leftNode.size { + return leftNode.GetByIndex(index) + } + return node.getRightNode().GetByIndex(index - leftNode.size) +} + +// Set inserts a new node with the given key-value pair into the subtree rooted at the node, +// and returns the new root of the subtree and whether an existing node was updated. +// +// XXX consider a better way to do this... perhaps split Node from Node. +func (node *Node) Set(key string, value any) (newSelf *Node, updated bool) { + if node == nil { + return NewNode(key, value), false + } + + // Always create a new node for leaf nodes + if node.height == 0 { + return node.setLeaf(key, value) + } + + // Copy the node before modifying + newNode := node._copy() + if key < node.key { + newNode.leftNode, updated = node.getLeftNode().Set(key, value) + } else { + newNode.rightNode, updated = node.getRightNode().Set(key, value) + } + + if !updated { + newNode.calcHeightAndSize() + return newNode.balance(), updated + } + + return newNode, updated +} + +// setLeaf inserts a new leaf node with the given key-value pair into the subtree rooted at the node, +// and returns the new root of the subtree and whether an existing node was updated. +func (node *Node) setLeaf(key string, value any) (newSelf *Node, updated bool) { + if key == node.key { + return NewNode(key, value), true + } + + if key < node.key { + return &Node{ + key: node.key, + height: 1, + size: 2, + leftNode: NewNode(key, value), + rightNode: node, + }, false + } + + return &Node{ + key: key, + height: 1, + size: 2, + leftNode: node, + rightNode: NewNode(key, value), + }, false +} + +// Remove deletes the node with the given key from the subtree rooted at the node. +// returns the new root of the subtree, the new leftmost leaf key (if changed), +// the removed value and the removal was successful. +func (node *Node) Remove(key string) ( + newNode *Node, newKey string, value any, removed bool, +) { + if node == nil { + return nil, "", nil, false + } + if node.height == 0 { + if key == node.key { + return nil, "", node.value, true + } + return node, "", nil, false + } + if key < node.key { + var newLeftNode *Node + newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) + if !removed { + return node, "", value, false + } + if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, true + } + node = node._copy() + node.leftNode = newLeftNode + node.calcHeightAndSize() + node = node.balance() + return node, newKey, value, true + } + + var newRightNode *Node + newRightNode, newKey, value, removed = node.getRightNode().Remove(key) + if !removed { + return node, "", value, false + } + if newRightNode == nil { // right node held value, was removed + return node.leftNode, "", value, true + } + node = node._copy() + node.rightNode = newRightNode + if newKey != "" { + node.key = newKey + } + node.calcHeightAndSize() + node = node.balance() + return node, "", value, true +} + +// getLeftNode returns the left child of the node. +func (node *Node) getLeftNode() *Node { + return node.leftNode +} + +// getRightNode returns the right child of the node. +func (node *Node) getRightNode() *Node { + return node.rightNode +} + +// rotateRight performs a right rotation on the node and returns the new root. +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateRight() *Node { + node = node._copy() + l := node.getLeftNode() + _l := l._copy() + + _lrCached := _l.rightNode + _l.rightNode = node + node.leftNode = _lrCached + + node.calcHeightAndSize() + _l.calcHeightAndSize() + + return _l +} + +// rotateLeft performs a left rotation on the node and returns the new root. +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateLeft() *Node { + node = node._copy() + r := node.getRightNode() + _r := r._copy() + + _rlCached := _r.leftNode + _r.leftNode = node + node.rightNode = _rlCached + + node.calcHeightAndSize() + _r.calcHeightAndSize() + + return _r +} + +// calcHeightAndSize updates the height and size of the node based on its children. +// NOTE: mutates height and size +func (node *Node) calcHeightAndSize() { + node.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1 + node.size = node.getLeftNode().size + node.getRightNode().size +} + +// calcBalance calculates the balance factor of the node. +func (node *Node) calcBalance() int { + return int(node.getLeftNode().height) - int(node.getRightNode().height) +} + +// balance balances the subtree rooted at the node and returns the new root. +// NOTE: assumes that node can be modified +// TODO: optimize balance & rotate +func (node *Node) balance() (newSelf *Node) { + balance := node.calcBalance() + if balance >= -1 { + return node + } + if balance > 1 { + if node.getLeftNode().calcBalance() >= 0 { + // Left Left Case + return node.rotateRight() + } + // Left Right Case + left := node.getLeftNode() + node.leftNode = left.rotateLeft() + return node.rotateRight() + } + + if node.getRightNode().calcBalance() <= 0 { + // Right Right Case + return node.rotateLeft() + } + + // Right Left Case + right := node.getRightNode() + node.rightNode = right.rotateRight() + return node.rotateLeft() +} + +// Shortcut for TraverseInRange. +func (node *Node) Iterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, true, true, cb) +} + +// Shortcut for TraverseInRange. +func (node *Node) ReverseIterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, false, true, cb) +} + +// TraverseInRange traverses all nodes, including inner nodes. +// Start is inclusive and end is exclusive when ascending, +// Start and end are inclusive when descending. +// Empty start and empty end denote no start and no end. +// If leavesOnly is true, only visit leaf nodes. +// NOTE: To simulate an exclusive reverse traversal, +// just append 0x00 to start. +func (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + afterStart := (start == "" || start < node.key) + startOrAfter := (start == "" || start <= node.key) + beforeEnd := false + if ascending { + beforeEnd = (end == "" || node.key < end) + } else { + beforeEnd = (end == "" || node.key <= end) + } + + // Run callback per inner/leaf node. + stop := false + if (!node.IsLeaf() && !leavesOnly) || + (node.IsLeaf() && startOrAfter && beforeEnd) { + stop = cb(node) + if stop { + return stop + } + } + if node.IsLeaf() { + return stop + } + + if ascending { + // check lower nodes, then higher + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } else { + // check the higher nodes first + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } + + return stop +} + +// TraverseByOffset traverses all nodes, including inner nodes. +// A limit of math.MaxInt means no limit. +func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + + // fast paths. these happen only if TraverseByOffset is called directly on a leaf. + if limit <= 0 || offset >= node.size { + return false + } + if node.IsLeaf() { + if offset > 0 { + return false + } + return cb(node) + } + + // go to the actual recursive function. + return node.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +// TraverseByOffset traverses the subtree rooted at the node by offset and limit, +// in either ascending or descending order, and applies the callback function to each traversed node. +// If leavesOnly is true, only leaf nodes are visited. +func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + // caller guarantees: offset < node.size; limit > 0. + if !leavesOnly { + if cb(node) { + return true + } + } + first, second := node.getLeftNode(), node.getRightNode() + if descending { + first, second = second, first + } + if first.IsLeaf() { + // either run or skip, based on offset + if offset > 0 { + offset-- + } else { + cb(first) + limit-- + if limit <= 0 { + return false + } + } + } else { + // possible cases: + // 1 the offset given skips the first node entirely + // 2 the offset skips none or part of the first node, but the limit requires some of the second node. + // 3 the offset skips none or part of the first node, and the limit stops our search on the first node. + if offset >= first.size { + offset -= first.size // 1 + } else { + if first.traverseByOffset(offset, limit, descending, leavesOnly, cb) { + return true + } + // number of leaves which could actually be called from inside + delta := first.size - offset + offset = 0 + if delta >= limit { + return true // 3 + } + limit -= delta // 2 + } + } + + // because of the caller guarantees and the way we handle the first node, + // at this point we know that limit > 0 and there must be some values in + // this second node that we include. + + // => if the second node is a leaf, it has to be included. + if second.IsLeaf() { + return cb(second) + } + // => if it is not a leaf, it will still be enough to recursively call this + // function with the updated offset and limit + return second.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +// Only used in testing... +func (node *Node) lmd() *Node { + if node.height == 0 { + return node + } + return node.getLeftNode().lmd() +} + +// Only used in testing... +func (node *Node) rmd() *Node { + if node.height == 0 { + return node + } + return node.getRightNode().rmd() +} + +func maxInt8(a, b int8) int8 { + if a > b { + return a + } + return b +} + +// Equal compares two nodes for structural equality. +// WARNING: This is an expensive operation that recursively traverses the entire tree structure. +// It should only be used in tests or when absolutely necessary. +func (node *Node) Equal(other *Node) bool { + // Handle nil cases + if node == nil || other == nil { + return node == other + } + + // Compare node properties + if node.key != other.key || + node.value != other.value || + node.height != other.height || + node.size != other.size { + return false + } + + // Compare children + leftEqual := (node.leftNode == nil && other.leftNode == nil) || + (node.leftNode != nil && other.leftNode != nil && node.leftNode.Equal(other.leftNode)) + if !leftEqual { + return false + } + + rightEqual := (node.rightNode == nil && other.rightNode == nil) || + (node.rightNode != nil && other.rightNode != nil && node.rightNode.Equal(other.rightNode)) + return rightEqual +} diff --git a/examples/gno.land/p/moul/cow/node_test.gno b/examples/gno.land/p/moul/cow/node_test.gno new file mode 100644 index 00000000000..b91aeb60cb7 --- /dev/null +++ b/examples/gno.land/p/moul/cow/node_test.gno @@ -0,0 +1,795 @@ +package cow + +import ( + "fmt" + "sort" + "strings" + "testing" +) + +func TestTraverseByOffset(t *testing.T) { + const testStrings = `Alfa +Alfred +Alpha +Alphabet +Beta +Beth +Book +Browser` + tt := []struct { + name string + desc bool + }{ + {"ascending", false}, + {"descending", true}, + } + + for _, tt := range tt { + t.Run(tt.name, func(t *testing.T) { + sl := strings.Split(testStrings, "\n") + + // sort a first time in the order opposite to how we'll be traversing + // the tree, to ensure that we are not just iterating through with + // insertion order. + sort.Strings(sl) + if !tt.desc { + reverseSlice(sl) + } + + r := NewNode(sl[0], nil) + for _, v := range sl[1:] { + r, _ = r.Set(v, nil) + } + + // then sort sl in the order we'll be traversing it, so that we can + // compare the result with sl. + reverseSlice(sl) + + var result []string + for i := 0; i < len(sl); i++ { + r.TraverseByOffset(i, 1, tt.desc, true, func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + } + + if !slicesEqual(sl, result) { + t.Errorf("want %v got %v", sl, result) + } + + for l := 2; l <= len(sl); l++ { + // "slices" + for i := 0; i <= len(sl); i++ { + max := i + l + if max > len(sl) { + max = len(sl) + } + exp := sl[i:max] + actual := []string{} + + r.TraverseByOffset(i, l, tt.desc, true, func(tr *Node) bool { + actual = append(actual, tr.Key()) + return false + }) + if !slicesEqual(exp, actual) { + t.Errorf("want %v got %v", exp, actual) + } + } + } + }) + } +} + +func TestHas(t *testing.T) { + tests := []struct { + name string + input []string + hasKey string + expected bool + }{ + { + "has key in non-empty tree", + []string{"C", "A", "B", "E", "D"}, + "B", + true, + }, + { + "does not have key in non-empty tree", + []string{"C", "A", "B", "E", "D"}, + "F", + false, + }, + { + "has key in single-node tree", + []string{"A"}, + "A", + true, + }, + { + "does not have key in single-node tree", + []string{"A"}, + "B", + false, + }, + { + "does not have key in empty tree", + []string{}, + "A", + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + result := tree.Has(tt.hasKey) + + if result != tt.expected { + t.Errorf("Expected %v, got %v", tt.expected, result) + } + }) + } +} + +func TestGet(t *testing.T) { + tests := []struct { + name string + input []string + getKey string + expectIdx int + expectVal any + expectExists bool + }{ + { + "get existing key", + []string{"C", "A", "B", "E", "D"}, + "B", + 1, + nil, + true, + }, + { + "get non-existent key (smaller)", + []string{"C", "A", "B", "E", "D"}, + "@", + 0, + nil, + false, + }, + { + "get non-existent key (larger)", + []string{"C", "A", "B", "E", "D"}, + "F", + 5, + nil, + false, + }, + { + "get from empty tree", + []string{}, + "A", + 0, + nil, + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + idx, val, exists := tree.Get(tt.getKey) + + if idx != tt.expectIdx { + t.Errorf("Expected index %d, got %d", tt.expectIdx, idx) + } + + if val != tt.expectVal { + t.Errorf("Expected value %v, got %v", tt.expectVal, val) + } + + if exists != tt.expectExists { + t.Errorf("Expected exists %t, got %t", tt.expectExists, exists) + } + }) + } +} + +func TestGetByIndex(t *testing.T) { + tests := []struct { + name string + input []string + idx int + expectKey string + expectVal any + expectPanic bool + }{ + { + "get by valid index", + []string{"C", "A", "B", "E", "D"}, + 2, + "C", + nil, + false, + }, + { + "get by valid index (smallest)", + []string{"C", "A", "B", "E", "D"}, + 0, + "A", + nil, + false, + }, + { + "get by valid index (largest)", + []string{"C", "A", "B", "E", "D"}, + 4, + "E", + nil, + false, + }, + { + "get by invalid index (negative)", + []string{"C", "A", "B", "E", "D"}, + -1, + "", + nil, + true, + }, + { + "get by invalid index (out of range)", + []string{"C", "A", "B", "E", "D"}, + 5, + "", + nil, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + if tt.expectPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected a panic but didn't get one") + } + }() + } + + key, val := tree.GetByIndex(tt.idx) + + if !tt.expectPanic { + if key != tt.expectKey { + t.Errorf("Expected key %s, got %s", tt.expectKey, key) + } + + if val != tt.expectVal { + t.Errorf("Expected value %v, got %v", tt.expectVal, val) + } + } + }) + } +} + +func TestRemove(t *testing.T) { + tests := []struct { + name string + input []string + removeKey string + expected []string + }{ + { + "remove leaf node", + []string{"C", "A", "B", "D"}, + "B", + []string{"A", "C", "D"}, + }, + { + "remove node with one child", + []string{"C", "A", "B", "D"}, + "A", + []string{"B", "C", "D"}, + }, + { + "remove node with two children", + []string{"C", "A", "B", "E", "D"}, + "C", + []string{"A", "B", "D", "E"}, + }, + { + "remove root node", + []string{"C", "A", "B", "E", "D"}, + "C", + []string{"A", "B", "D", "E"}, + }, + { + "remove non-existent key", + []string{"C", "A", "B", "E", "D"}, + "F", + []string{"A", "B", "C", "D", "E"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + tree, _, _, _ = tree.Remove(tt.removeKey) + + result := make([]string, 0) + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + } +} + +func TestTraverse(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + "empty tree", + []string{}, + []string{}, + }, + { + "single node tree", + []string{"A"}, + []string{"A"}, + }, + { + "small tree", + []string{"C", "A", "B", "E", "D"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "large tree", + []string{"H", "D", "L", "B", "F", "J", "N", "A", "C", "E", "G", "I", "K", "M", "O"}, + []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + t.Run("iterate", func(t *testing.T) { + var result []string + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + + t.Run("ReverseIterate", func(t *testing.T) { + var result []string + tree.ReverseIterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + expected := make([]string, len(tt.expected)) + copy(expected, tt.expected) + for i, j := 0, len(expected)-1; i < j; i, j = i+1, j-1 { + expected[i], expected[j] = expected[j], expected[i] + } + if !slicesEqual(expected, result) { + t.Errorf("want %v got %v", expected, result) + } + }) + + t.Run("TraverseInRange", func(t *testing.T) { + var result []string + start, end := "C", "M" + tree.TraverseInRange(start, end, true, true, func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + expected := make([]string, 0) + for _, key := range tt.expected { + if key >= start && key < end { + expected = append(expected, key) + } + } + if !slicesEqual(expected, result) { + t.Errorf("want %v got %v", expected, result) + } + }) + }) + } +} + +func TestRotateWhenHeightDiffers(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + "right rotation when left subtree is higher", + []string{"E", "C", "A", "B", "D"}, + []string{"A", "B", "C", "E", "D"}, + }, + { + "left rotation when right subtree is higher", + []string{"A", "C", "E", "D", "F"}, + []string{"A", "C", "D", "E", "F"}, + }, + { + "left-right rotation", + []string{"E", "A", "C", "B", "D"}, + []string{"A", "B", "C", "E", "D"}, + }, + { + "right-left rotation", + []string{"A", "E", "C", "B", "D"}, + []string{"A", "B", "C", "E", "D"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + // perform rotation or balance + tree = tree.balance() + + // check tree structure + var result []string + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + } +} + +func TestRotateAndBalance(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + "right rotation", + []string{"A", "B", "C", "D", "E"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "left rotation", + []string{"E", "D", "C", "B", "A"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "left-right rotation", + []string{"C", "A", "E", "B", "D"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "right-left rotation", + []string{"C", "E", "A", "D", "B"}, + []string{"A", "B", "C", "D", "E"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + tree = tree.balance() + + var result []string + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + } +} + +func slicesEqual(w1, w2 []string) bool { + if len(w1) != len(w2) { + return false + } + for i := 0; i < len(w1); i++ { + if w1[0] != w2[0] { + return false + } + } + return true +} + +func maxint8(a, b int8) int8 { + if a > b { + return a + } + return b +} + +func reverseSlice(ss []string) { + for i := 0; i < len(ss)/2; i++ { + j := len(ss) - 1 - i + ss[i], ss[j] = ss[j], ss[i] + } +} + +func TestNodeStructuralSharing(t *testing.T) { + t.Run("unmodified paths remain shared", func(t *testing.T) { + root := NewNode("B", 2) + root, _ = root.Set("A", 1) + root, _ = root.Set("C", 3) + + originalRight := root.rightNode + newRoot, _ := root.Set("A", 10) + + if newRoot.rightNode != originalRight { + t.Error("Unmodified right subtree should remain shared") + } + }) + + t.Run("multiple modifications reuse shared structure", func(t *testing.T) { + // Create initial tree + root := NewNode("B", 2) + root, _ = root.Set("A", 1) + root, _ = root.Set("C", 3) + + // Store original nodes + originalRight := root.rightNode + + // First modification + mod1, _ := root.Set("A", 10) + + // Second modification + mod2, _ := mod1.Set("C", 30) + + // Check sharing in first modification + if mod1.rightNode != originalRight { + t.Error("First modification should share unmodified right subtree") + } + + // Check that second modification creates new right node + if mod2.rightNode == originalRight { + t.Error("Second modification should create new right node") + } + }) +} + +func TestNodeCopyOnWrite(t *testing.T) { + t.Run("copy preserves structure", func(t *testing.T) { + root := NewNode("B", 2) + root, _ = root.Set("A", 1) + root, _ = root.Set("C", 3) + + // Only copy non-leaf nodes + if !root.IsLeaf() { + copied := root._copy() + if copied == root { + t.Error("Copy should create new instance") + } + + // Create temporary trees to use Equal method + original := &Tree{node: root} + copiedTree := &Tree{node: copied} + if !original.Equal(copiedTree) { + t.Error("Copied node should preserve structure") + } + } + }) + + t.Run("removal copy pattern", func(t *testing.T) { + // Create a more complex tree to test removal + root := NewNode("B", 2) + root, _ = root.Set("A", 1) + root, _ = root.Set("C", 3) + root, _ = root.Set("D", 4) // Add this to ensure proper tree structure + + // Store references to original nodes + originalRight := root.rightNode + originalRightRight := originalRight.rightNode + + // Remove "A" which should only affect the left subtree + newRoot, _, _, _ := root.Remove("A") + + // Verify right subtree remains unchanged and shared + if newRoot.rightNode != originalRight { + t.Error("Right subtree should remain shared during removal of left node") + } + + // Also verify deeper nodes remain shared + if newRoot.rightNode.rightNode != originalRightRight { + t.Error("Deep right subtree should remain shared during removal") + } + + // Verify original tree is unchanged + if _, _, exists := root.Get("A"); !exists { + t.Error("Original tree should remain unchanged") + } + }) + + t.Run("copy leaf node panic", func(t *testing.T) { + leaf := NewNode("A", 1) + + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic when copying leaf node") + } + }() + + // This should panic with our specific message + leaf._copy() + }) +} + +func TestNodeEqual(t *testing.T) { + tests := []struct { + name string + node1 func() *Node + node2 func() *Node + expected bool + }{ + { + name: "nil nodes", + node1: func() *Node { return nil }, + node2: func() *Node { return nil }, + expected: true, + }, + { + name: "one nil node", + node1: func() *Node { return NewNode("A", 1) }, + node2: func() *Node { return nil }, + expected: false, + }, + { + name: "single leaf nodes equal", + node1: func() *Node { return NewNode("A", 1) }, + node2: func() *Node { return NewNode("A", 1) }, + expected: true, + }, + { + name: "single leaf nodes different key", + node1: func() *Node { return NewNode("A", 1) }, + node2: func() *Node { return NewNode("B", 1) }, + expected: false, + }, + { + name: "single leaf nodes different value", + node1: func() *Node { return NewNode("A", 1) }, + node2: func() *Node { return NewNode("A", 2) }, + expected: false, + }, + { + name: "complex trees equal", + node1: func() *Node { + n, _ := NewNode("B", 2).Set("A", 1) + n, _ = n.Set("C", 3) + return n + }, + node2: func() *Node { + n, _ := NewNode("B", 2).Set("A", 1) + n, _ = n.Set("C", 3) + return n + }, + expected: true, + }, + { + name: "complex trees different structure", + node1: func() *Node { + // Create a tree with structure: + // B + // / \ + // A D + n := NewNode("B", 2) + n, _ = n.Set("A", 1) + n, _ = n.Set("D", 4) + return n + }, + node2: func() *Node { + // Create a tree with structure: + // C + // / \ + // A D + n := NewNode("C", 3) + n, _ = n.Set("A", 1) + n, _ = n.Set("D", 4) + return n + }, + expected: false, // These trees should be different + }, + { + name: "complex trees same structure despite different insertion order", + node1: func() *Node { + n, _ := NewNode("B", 2).Set("A", 1) + n, _ = n.Set("C", 3) + return n + }, + node2: func() *Node { + n, _ := NewNode("A", 1).Set("B", 2) + n, _ = n.Set("C", 3) + return n + }, + expected: true, + }, + { + name: "truly different structures", + node1: func() *Node { + n, _ := NewNode("B", 2).Set("A", 1) + return n // Tree with just two nodes + }, + node2: func() *Node { + n, _ := NewNode("B", 2).Set("C", 3) + return n // Different two-node tree + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + node1 := tt.node1() + node2 := tt.node2() + result := node1.Equal(node2) + if result != tt.expected { + t.Errorf("Expected Equal to return %v for %s", tt.expected, tt.name) + println("\nComparison failed:") + println("Tree 1:") + printTree(node1, 0) + println("Tree 2:") + printTree(node2, 0) + } + }) + } +} + +// Helper function to print tree structure +func printTree(node *Node, level int) { + if node == nil { + return + } + indent := strings.Repeat(" ", level) + println(fmt.Sprintf("%sKey: %s, Value: %v, Height: %d, Size: %d", + indent, node.key, node.value, node.height, node.size)) + printTree(node.leftNode, level+1) + printTree(node.rightNode, level+1) +} diff --git a/examples/gno.land/p/moul/cow/tree.gno b/examples/gno.land/p/moul/cow/tree.gno new file mode 100644 index 00000000000..086977580c3 --- /dev/null +++ b/examples/gno.land/p/moul/cow/tree.gno @@ -0,0 +1,164 @@ +// Package cow provides a Copy-on-Write (CoW) AVL tree implementation. +// This is a fork of gno.land/p/demo/avl that adds CoW functionality +// while maintaining the original AVL tree interface and properties. +// +// Copy-on-Write creates a copy of a data structure only when it is modified, +// while still presenting the appearance of a full copy. When a tree is cloned, +// it initially shares all its nodes with the original tree. Only when a +// modification is made to either the original or the clone are new nodes created, +// and only along the path from the root to the modified node. +// +// Key features: +// - O(1) cloning operation +// - Minimal memory usage through structural sharing +// - Full AVL tree functionality (self-balancing, ordered operations) +// - Thread-safe for concurrent reads of shared structures +// +// While the CoW mechanism handles structural copying automatically, users need +// to consider how to handle the values stored in the tree: +// +// 1. Simple Values (int, string, etc.): +// - These are copied by value automatically +// - No additional handling needed +// +// 2. Complex Values (structs, pointers): +// - Only the reference is copied by default +// - Users must implement their own deep copy mechanism if needed +// +// Example: +// +// // Create original tree +// original := cow.NewTree() +// original.Set("key1", "value1") +// +// // Create a clone - O(1) operation +// clone := original.Clone() +// +// // Modify clone - only affected nodes are copied +// clone.Set("key1", "modified") +// +// // Original remains unchanged +// val, _ := original.Get("key1") // Returns "value1" +package cow + +type IterCbFn func(key string, value any) bool + +//---------------------------------------- +// Tree + +// The zero struct can be used as an empty tree. +type Tree struct { + node *Node +} + +// NewTree creates a new empty AVL tree. +func NewTree() *Tree { + return &Tree{ + node: nil, + } +} + +// Size returns the number of key-value pair in the tree. +func (tree *Tree) Size() int { + return tree.node.Size() +} + +// Has checks whether a key exists in the tree. +// It returns true if the key exists, otherwise false. +func (tree *Tree) Has(key string) (has bool) { + return tree.node.Has(key) +} + +// Get retrieves the value associated with the given key. +// It returns the value and a boolean indicating whether the key exists. +func (tree *Tree) Get(key string) (value any, exists bool) { + _, value, exists = tree.node.Get(key) + return +} + +// GetByIndex retrieves the key-value pair at the specified index in the tree. +// It returns the key and value at the given index. +func (tree *Tree) GetByIndex(index int) (key string, value any) { + return tree.node.GetByIndex(index) +} + +// Set inserts a key-value pair into the tree. +// If the key already exists, the value will be updated. +// It returns a boolean indicating whether the key was newly inserted or updated. +func (tree *Tree) Set(key string, value any) (updated bool) { + newnode, updated := tree.node.Set(key, value) + tree.node = newnode + return updated +} + +// Remove removes a key-value pair from the tree. +// It returns the removed value and a boolean indicating whether the key was found and removed. +func (tree *Tree) Remove(key string) (value any, removed bool) { + newnode, _, value, removed := tree.node.Remove(key) + tree.node = newnode + return value, removed +} + +// Iterate performs an in-order traversal of the tree within the specified key range. +// It calls the provided callback function for each key-value pair encountered. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) Iterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// ReverseIterate performs a reverse in-order traversal of the tree within the specified key range. +// It calls the provided callback function for each key-value pair encountered. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// IterateByOffset performs an in-order traversal of the tree starting from the specified offset. +// It calls the provided callback function for each key-value pair encountered, up to the specified count. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// ReverseIterateByOffset performs a reverse in-order traversal of the tree starting from the specified offset. +// It calls the provided callback function for each key-value pair encountered, up to the specified count. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Equal returns true if the two trees contain the same key-value pairs. +// WARNING: This is an expensive operation that recursively traverses the entire tree structure. +// It should only be used in tests or when absolutely necessary. +func (tree *Tree) Equal(other *Tree) bool { + if tree == nil || other == nil { + return tree == other + } + return tree.node.Equal(other.node) +} + +// Clone creates a shallow copy of the tree +func (tree *Tree) Clone() *Tree { + if tree == nil { + return nil + } + return &Tree{ + node: tree.node, + } +} diff --git a/examples/gno.land/p/moul/cow/tree_test.gno b/examples/gno.land/p/moul/cow/tree_test.gno new file mode 100644 index 00000000000..da0356a37aa --- /dev/null +++ b/examples/gno.land/p/moul/cow/tree_test.gno @@ -0,0 +1,392 @@ +package cow + +import ( + "testing" +) + +func TestNewTree(t *testing.T) { + tree := NewTree() + if tree.node != nil { + t.Error("Expected tree.node to be nil") + } +} + +func TestTreeSize(t *testing.T) { + tree := NewTree() + if tree.Size() != 0 { + t.Error("Expected empty tree size to be 0") + } + + tree.Set("key1", "value1") + tree.Set("key2", "value2") + if tree.Size() != 2 { + t.Error("Expected tree size to be 2") + } +} + +func TestTreeHas(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + + if !tree.Has("key1") { + t.Error("Expected tree to have key1") + } + + if tree.Has("key2") { + t.Error("Expected tree to not have key2") + } +} + +func TestTreeGet(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + + value, exists := tree.Get("key1") + if !exists || value != "value1" { + t.Error("Expected Get to return value1 and true") + } + + _, exists = tree.Get("key2") + if exists { + t.Error("Expected Get to return false for non-existent key") + } +} + +func TestTreeGetByIndex(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + + key, value := tree.GetByIndex(0) + if key != "key1" || value != "value1" { + t.Error("Expected GetByIndex(0) to return key1 and value1") + } + + key, value = tree.GetByIndex(1) + if key != "key2" || value != "value2" { + t.Error("Expected GetByIndex(1) to return key2 and value2") + } + + defer func() { + if r := recover(); r == nil { + t.Error("Expected GetByIndex to panic for out-of-range index") + } + }() + tree.GetByIndex(2) +} + +func TestTreeRemove(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + + value, removed := tree.Remove("key1") + if !removed || value != "value1" || tree.Size() != 0 { + t.Error("Expected Remove to remove key-value pair") + } + + _, removed = tree.Remove("key2") + if removed { + t.Error("Expected Remove to return false for non-existent key") + } +} + +func TestTreeIterate(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.Iterate("", "", func(key string, value any) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key1", "key2", "key3"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +func TestTreeReverseIterate(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.ReverseIterate("", "", func(key string, value any) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key3", "key2", "key1"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +func TestTreeIterateByOffset(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.IterateByOffset(1, 2, func(key string, value any) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key2", "key3"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +func TestTreeReverseIterateByOffset(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.ReverseIterateByOffset(1, 2, func(key string, value any) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key2", "key1"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +// Verify that Tree implements avl.ITree +// var _ avl.ITree = (*Tree)(nil) // TODO: fix gnovm bug: ./examples/gno.land/p/moul/cow: test pkg: panic: gno.land/p/moul/cow/tree_test.gno:166:5: name avl not defined in fileset with files [node.gno tree.gno node_test.gno tree_test.gno]: + +func TestCopyOnWrite(t *testing.T) { + // Create original tree + original := NewTree() + original.Set("A", 1) + original.Set("B", 2) + original.Set("C", 3) + + // Create a clone + clone := original.Clone() + + // Modify clone + clone.Set("B", 20) + clone.Set("D", 4) + + // Verify original is unchanged + if val, _ := original.Get("B"); val != 2 { + t.Errorf("Original tree was modified: expected B=2, got B=%v", val) + } + if original.Has("D") { + t.Error("Original tree was modified: found key D") + } + + // Verify clone has new values + if val, _ := clone.Get("B"); val != 20 { + t.Errorf("Clone not updated: expected B=20, got B=%v", val) + } + if val, _ := clone.Get("D"); val != 4 { + t.Errorf("Clone not updated: expected D=4, got D=%v", val) + } +} + +func TestCopyOnWriteEdgeCases(t *testing.T) { + t.Run("nil tree clone", func(t *testing.T) { + var original *Tree + clone := original.Clone() + if clone != nil { + t.Error("Expected nil clone from nil tree") + } + }) + + t.Run("empty tree clone", func(t *testing.T) { + original := NewTree() + clone := original.Clone() + + // Modify clone + clone.Set("A", 1) + + if original.Size() != 0 { + t.Error("Original empty tree was modified") + } + if clone.Size() != 1 { + t.Error("Clone was not modified") + } + }) + + t.Run("multiple clones", func(t *testing.T) { + original := NewTree() + original.Set("A", 1) + original.Set("B", 2) + + // Create multiple clones + clone1 := original.Clone() + clone2 := original.Clone() + clone3 := clone1.Clone() + + // Modify each clone differently + clone1.Set("A", 10) + clone2.Set("B", 20) + clone3.Set("C", 30) + + // Check original remains unchanged + if val, _ := original.Get("A"); val != 1 { + t.Errorf("Original modified: expected A=1, got A=%v", val) + } + if val, _ := original.Get("B"); val != 2 { + t.Errorf("Original modified: expected B=2, got B=%v", val) + } + + // Verify each clone has correct values + if val, _ := clone1.Get("A"); val != 10 { + t.Errorf("Clone1 incorrect: expected A=10, got A=%v", val) + } + if val, _ := clone2.Get("B"); val != 20 { + t.Errorf("Clone2 incorrect: expected B=20, got B=%v", val) + } + if val, _ := clone3.Get("C"); val != 30 { + t.Errorf("Clone3 incorrect: expected C=30, got C=%v", val) + } + }) + + t.Run("clone after removal", func(t *testing.T) { + original := NewTree() + original.Set("A", 1) + original.Set("B", 2) + original.Set("C", 3) + + // Remove a node and then clone + original.Remove("B") + clone := original.Clone() + + // Modify clone + clone.Set("B", 20) + + // Verify original state + if original.Has("B") { + t.Error("Original tree should not have key B") + } + + // Verify clone state + if val, _ := clone.Get("B"); val != 20 { + t.Errorf("Clone incorrect: expected B=20, got B=%v", val) + } + }) + + t.Run("concurrent modifications", func(t *testing.T) { + original := NewTree() + original.Set("A", 1) + original.Set("B", 2) + + clone1 := original.Clone() + clone2 := original.Clone() + + // Modify same key in different clones + clone1.Set("B", 20) + clone2.Set("B", 30) + + // Each clone should have its own value + if val, _ := clone1.Get("B"); val != 20 { + t.Errorf("Clone1 incorrect: expected B=20, got B=%v", val) + } + if val, _ := clone2.Get("B"); val != 30 { + t.Errorf("Clone2 incorrect: expected B=30, got B=%v", val) + } + }) + + t.Run("deep tree modifications", func(t *testing.T) { + original := NewTree() + // Create a deeper tree + keys := []string{"M", "F", "T", "B", "H", "P", "Z"} + for _, k := range keys { + original.Set(k, k) + } + + clone := original.Clone() + + // Modify a deep node + clone.Set("H", "modified") + + // Check original remains unchanged + if val, _ := original.Get("H"); val != "H" { + t.Errorf("Original modified: expected H='H', got H=%v", val) + } + + // Verify clone modification + if val, _ := clone.Get("H"); val != "modified" { + t.Errorf("Clone incorrect: expected H='modified', got H=%v", val) + } + }) + + t.Run("rebalancing test", func(t *testing.T) { + original := NewTree() + // Insert nodes that will cause rotations + keys := []string{"A", "B", "C", "D", "E"} + for _, k := range keys { + original.Set(k, k) + } + + clone := original.Clone() + + // Add more nodes to clone to trigger rebalancing + clone.Set("F", "F") + clone.Set("G", "G") + + // Verify original structure remains unchanged + originalKeys := collectKeys(original) + expectedOriginal := []string{"A", "B", "C", "D", "E"} + if !slicesEqual(originalKeys, expectedOriginal) { + t.Errorf("Original tree structure changed: got %v, want %v", originalKeys, expectedOriginal) + } + + // Verify clone has all keys + cloneKeys := collectKeys(clone) + expectedClone := []string{"A", "B", "C", "D", "E", "F", "G"} + if !slicesEqual(cloneKeys, expectedClone) { + t.Errorf("Clone tree structure incorrect: got %v, want %v", cloneKeys, expectedClone) + } + }) + + t.Run("value mutation test", func(t *testing.T) { + type MutableValue struct { + Data string + } + + original := NewTree() + mutable := &MutableValue{Data: "original"} + original.Set("key", mutable) + + clone := original.Clone() + + // Modify the mutable value + mutable.Data = "modified" + + // Both original and clone should see the modification + // because we're not deep copying values + origVal, _ := original.Get("key") + cloneVal, _ := clone.Get("key") + + if origVal.(*MutableValue).Data != "modified" { + t.Error("Original value not modified as expected") + } + if cloneVal.(*MutableValue).Data != "modified" { + t.Error("Clone value not modified as expected") + } + }) +} + +// Helper function to collect all keys in order +func collectKeys(tree *Tree) []string { + var keys []string + tree.Iterate("", "", func(key string, _ any) bool { + keys = append(keys, key) + return false + }) + return keys +} diff --git a/examples/gno.land/p/moul/debug/debug.gno b/examples/gno.land/p/moul/debug/debug.gno index 9ba3dd36a98..3bfe612c780 100644 --- a/examples/gno.land/p/moul/debug/debug.gno +++ b/examples/gno.land/p/moul/debug/debug.gno @@ -50,10 +50,10 @@ func (d Debug) Render(path string) string { Headers: []string{"Key", "Value"}, } table.Append([]string{"`std.CurrentRealm().PkgPath()`", string(std.CurrentRealm().PkgPath())}) - table.Append([]string{"`std.CurrentRealm().Addr()`", string(std.CurrentRealm().Addr())}) - table.Append([]string{"`std.PrevRealm().PkgPath()`", string(std.PrevRealm().PkgPath())}) - table.Append([]string{"`std.PrevRealm().Addr()`", string(std.PrevRealm().Addr())}) - table.Append([]string{"`std.GetHeight()`", ufmt.Sprintf("%d", std.GetHeight())}) + table.Append([]string{"`std.CurrentRealm().Address()`", string(std.CurrentRealm().Address())}) + table.Append([]string{"`std.PreviousRealm().PkgPath()`", string(std.PreviousRealm().PkgPath())}) + table.Append([]string{"`std.PreviousRealm().Address()`", string(std.PreviousRealm().Address())}) + table.Append([]string{"`std.ChainHeight()`", ufmt.Sprintf("%d", std.ChainHeight())}) table.Append([]string{"`time.Now().Format(time.RFC3339)`", time.Now().Format(time.RFC3339)}) content += table.String() } diff --git a/examples/gno.land/p/moul/debug/z1_filetest.gno b/examples/gno.land/p/moul/debug/z1_filetest.gno index 8203749d3c7..5016fdab605 100644 --- a/examples/gno.land/p/moul/debug/z1_filetest.gno +++ b/examples/gno.land/p/moul/debug/z1_filetest.gno @@ -20,10 +20,10 @@ func main() { // | Key | Value | // | --- | --- | // | `std.CurrentRealm().PkgPath()` | | -// | `std.CurrentRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | -// | `std.PrevRealm().PkgPath()` | | -// | `std.PrevRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | -// | `std.GetHeight()` | 123 | +// | `std.CurrentRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | +// | `std.PreviousRealm().PkgPath()` | | +// | `std.PreviousRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | +// | `std.ChainHeight()` | 123 | // | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z | // //
diff --git a/examples/gno.land/p/moul/debug/z2_filetest.gno b/examples/gno.land/p/moul/debug/z2_filetest.gno index 32c2fe49951..fab7e57cdc2 100644 --- a/examples/gno.land/p/moul/debug/z2_filetest.gno +++ b/examples/gno.land/p/moul/debug/z2_filetest.gno @@ -26,10 +26,10 @@ func main() { // | Key | Value | // | --- | --- | // | `std.CurrentRealm().PkgPath()` | | -// | `std.CurrentRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | -// | `std.PrevRealm().PkgPath()` | | -// | `std.PrevRealm().Addr()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | -// | `std.GetHeight()` | 123 | +// | `std.CurrentRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | +// | `std.PreviousRealm().PkgPath()` | | +// | `std.PreviousRealm().Address()` | g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm | +// | `std.ChainHeight()` | 123 | // | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z | // //
diff --git a/examples/gno.land/p/moul/dynreplacer/dynreplacer.gno b/examples/gno.land/p/moul/dynreplacer/dynreplacer.gno new file mode 100644 index 00000000000..ea9687b7570 --- /dev/null +++ b/examples/gno.land/p/moul/dynreplacer/dynreplacer.gno @@ -0,0 +1,111 @@ +// Package dynreplacer provides a simple template engine for handling dynamic +// content replacement. It is similar to strings.Replacer but with lazy +// execution of replacements, making it more optimization-friendly in several +// cases. While strings.Replacer requires all replacement values to be computed +// upfront, dynreplacer only executes the callback functions for placeholders +// that actually exist in the template, avoiding unnecessary computations. +// +// The package ensures efficient, non-recursive replacement of placeholders in a +// single pass. This lazy evaluation approach is particularly beneficial when: +// - Some replacement values are expensive to compute +// - Not all placeholders are guaranteed to be present in the template +// - Templates are reused with different content +// +// Example usage: +// +// r := dynreplacer.New( +// dynreplacer.Pair{":name:", func() string { return "World" }}, +// dynreplacer.Pair{":greeting:", func() string { return "Hello" }}, +// ) +// result := r.Replace("Hello :name:!") // Returns "Hello World!" +// +// The replacer caches computed values, so subsequent calls with the same +// placeholder will reuse the cached value instead of executing the callback +// again: +// +// r := dynreplacer.New() +// r.RegisterCallback(":expensive:", func() string { return "computed" }) +// r.Replace("Value1: :expensive:") // Computes the value +// r.Replace("Value2: :expensive:") // Uses cached value +// r.ClearCache() // Force re-computation on next use +package dynreplacer + +import ( + "strings" +) + +// Replacer manages dynamic placeholders, their associated functions, and cached +// values. +type Replacer struct { + callbacks map[string]func() string + cachedValues map[string]string +} + +// Pair represents a placeholder and its callback function +type Pair struct { + Placeholder string + Callback func() string +} + +// New creates a new Replacer instance with optional initial replacements. +// It accepts pairs where each pair consists of a placeholder string and +// its corresponding callback function. +// +// Example: +// +// New( +// Pair{":name:", func() string { return "World" }}, +// Pair{":greeting:", func() string { return "Hello" }}, +// ) +func New(pairs ...Pair) *Replacer { + r := &Replacer{ + callbacks: make(map[string]func() string), + cachedValues: make(map[string]string), + } + + for _, pair := range pairs { + r.RegisterCallback(pair.Placeholder, pair.Callback) + } + + return r +} + +// RegisterCallback associates a placeholder with a function to generate its +// content. +func (r *Replacer) RegisterCallback(placeholder string, callback func() string) { + r.callbacks[placeholder] = callback +} + +// Replace processes the given layout, replacing placeholders with cached or +// newly computed values. +func (r *Replacer) Replace(layout string) string { + replacements := []string{} + + // Check for placeholders and compute/retrieve values + hasReplacements := false + for placeholder, callback := range r.callbacks { + if strings.Contains(layout, placeholder) { + value, exists := r.cachedValues[placeholder] + if !exists { + value = callback() + r.cachedValues[placeholder] = value + } + replacements = append(replacements, placeholder, value) + hasReplacements = true + } + } + + // If no replacements were found, return the original layout + if !hasReplacements { + return layout + } + + // Create a strings.Replacer with all computed replacements + replacer := strings.NewReplacer(replacements...) + return replacer.Replace(layout) +} + +// ClearCache clears all cached values, forcing re-computation on next Replace. +func (r *Replacer) ClearCache() { + r.cachedValues = make(map[string]string) +} diff --git a/examples/gno.land/p/moul/dynreplacer/dynreplacer_test.gno b/examples/gno.land/p/moul/dynreplacer/dynreplacer_test.gno new file mode 100644 index 00000000000..51235a28950 --- /dev/null +++ b/examples/gno.land/p/moul/dynreplacer/dynreplacer_test.gno @@ -0,0 +1,235 @@ +package dynreplacer + +import ( + "strings" + "testing" + + "gno.land/p/demo/uassert" +) + +func TestNew(t *testing.T) { + tests := []struct { + name string + pairs []Pair + }{ + { + name: "empty constructor", + pairs: []Pair{}, + }, + { + name: "single pair", + pairs: []Pair{ + {":name:", func() string { return "World" }}, + }, + }, + { + name: "multiple pairs", + pairs: []Pair{ + {":greeting:", func() string { return "Hello" }}, + {":name:", func() string { return "World" }}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := New(tt.pairs...) + uassert.True(t, r.callbacks != nil, "callbacks map should be initialized") + uassert.True(t, r.cachedValues != nil, "cachedValues map should be initialized") + + // Verify all callbacks were registered + for _, pair := range tt.pairs { + _, exists := r.callbacks[pair.Placeholder] + uassert.True(t, exists, "callback should be registered for "+pair.Placeholder) + } + }) + } +} + +func TestReplace(t *testing.T) { + tests := []struct { + name string + layout string + setup func(*Replacer) + expected string + }{ + { + name: "empty layout", + layout: "", + setup: func(r *Replacer) {}, + expected: "", + }, + { + name: "single replacement", + layout: "Hello :name:!", + setup: func(r *Replacer) { + r.RegisterCallback(":name:", func() string { return "World" }) + }, + expected: "Hello World!", + }, + { + name: "multiple replacements", + layout: ":greeting: :name:!", + setup: func(r *Replacer) { + r.RegisterCallback(":greeting:", func() string { return "Hello" }) + r.RegisterCallback(":name:", func() string { return "World" }) + }, + expected: "Hello World!", + }, + { + name: "no recursive replacement", + layout: ":outer:", + setup: func(r *Replacer) { + r.RegisterCallback(":outer:", func() string { return ":inner:" }) + r.RegisterCallback(":inner:", func() string { return "content" }) + }, + expected: ":inner:", + }, + { + name: "unused callbacks", + layout: "Hello :name:!", + setup: func(r *Replacer) { + r.RegisterCallback(":name:", func() string { return "World" }) + r.RegisterCallback(":unused:", func() string { return "Never Called" }) + }, + expected: "Hello World!", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := New() + tt.setup(r) + result := r.Replace(tt.layout) + uassert.Equal(t, tt.expected, result) + }) + } +} + +func TestCaching(t *testing.T) { + r := New() + callCount := 0 + r.RegisterCallback(":expensive:", func() string { + callCount++ + return "computed" + }) + + layout := "Value: :expensive:" + + // First call should compute + result1 := r.Replace(layout) + uassert.Equal(t, "Value: computed", result1) + uassert.Equal(t, 1, callCount) + + // Second call should use cache + result2 := r.Replace(layout) + uassert.Equal(t, "Value: computed", result2) + uassert.Equal(t, 1, callCount) + + // After clearing cache, should recompute + r.ClearCache() + result3 := r.Replace(layout) + uassert.Equal(t, "Value: computed", result3) + uassert.Equal(t, 2, callCount) +} + +func TestComplexExample(t *testing.T) { + layout := ` + # Welcome to gno.land + + ## Blog + :latest-blogposts: + + ## Events + :next-events: + + ## Awesome Gno + :awesome-gno: + ` + + r := New( + Pair{":latest-blogposts:", func() string { return "Latest blog posts content here" }}, + Pair{":next-events:", func() string { return "Upcoming events listed here" }}, + Pair{":awesome-gno:", func() string { return ":latest-blogposts: (This should NOT be replaced again)" }}, + ) + + result := r.Replace(layout) + + // Check that original placeholders are replaced + uassert.True(t, !strings.Contains(result, ":latest-blogposts:\n"), "':latest-blogposts:' placeholder should be replaced") + uassert.True(t, !strings.Contains(result, ":next-events:\n"), "':next-events:' placeholder should be replaced") + uassert.True(t, !strings.Contains(result, ":awesome-gno:\n"), "':awesome-gno:' placeholder should be replaced") + + // Check that the replacement content is present + uassert.True(t, strings.Contains(result, "Latest blog posts content here"), "Blog posts content should be present") + uassert.True(t, strings.Contains(result, "Upcoming events listed here"), "Events content should be present") + uassert.True(t, strings.Contains(result, ":latest-blogposts: (This should NOT be replaced again)"), + "Nested placeholder should not be replaced") +} + +func TestEdgeCases(t *testing.T) { + tests := []struct { + name string + layout string + setup func(*Replacer) + expected string + }{ + { + name: "empty string placeholder", + layout: "Hello :", + setup: func(r *Replacer) { + r.RegisterCallback("", func() string { return "World" }) + }, + expected: "WorldHWorldeWorldlWorldlWorldoWorld World:World", + }, + { + name: "overlapping placeholders", + layout: "Hello :name::greeting:", + setup: func(r *Replacer) { + r.RegisterCallback(":name:", func() string { return "World" }) + r.RegisterCallback(":greeting:", func() string { return "Hi" }) + r.RegisterCallback(":name::greeting:", func() string { return "Should not match" }) + }, + expected: "Hello WorldHi", + }, + { + name: "replacement order", + layout: ":a::b::c:", + setup: func(r *Replacer) { + r.RegisterCallback(":c:", func() string { return "3" }) + r.RegisterCallback(":b:", func() string { return "2" }) + r.RegisterCallback(":a:", func() string { return "1" }) + }, + expected: "123", + }, + { + name: "special characters in placeholders", + layout: "Hello :$name#123:!", + setup: func(r *Replacer) { + r.RegisterCallback(":$name#123:", func() string { return "World" }) + }, + expected: "Hello World!", + }, + { + name: "multiple occurrences of same placeholder", + layout: ":name: and :name: again", + setup: func(r *Replacer) { + callCount := 0 + r.RegisterCallback(":name:", func() string { + callCount++ + return "World" + }) + }, + expected: "World and World again", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := New() + tt.setup(r) + result := r.Replace(tt.layout) + uassert.Equal(t, tt.expected, result) + }) + } +} diff --git a/examples/gno.land/p/moul/dynreplacer/gno.mod b/examples/gno.land/p/moul/dynreplacer/gno.mod new file mode 100644 index 00000000000..9e196721f24 --- /dev/null +++ b/examples/gno.land/p/moul/dynreplacer/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/dynreplacer diff --git a/examples/gno.land/p/moul/fifo/fifo.gno b/examples/gno.land/p/moul/fifo/fifo.gno new file mode 100644 index 00000000000..e3d73b35dac --- /dev/null +++ b/examples/gno.land/p/moul/fifo/fifo.gno @@ -0,0 +1,250 @@ +// Package fifo implements a fixed-size FIFO (First-In-First-Out) list data structure +// using a singly-linked list. The implementation prioritizes storage efficiency by minimizing +// storage operations - each add/remove operation only updates 1-2 pointers, regardless of +// list size. +// +// Key features: +// - Fixed-size with automatic removal of oldest entries when full +// - Support for both prepend (add at start) and append (add at end) operations +// - Constant storage usage through automatic pruning +// - O(1) append operations and latest element access +// - Iterator support for sequential access +// - Dynamic size adjustment via SetMaxSize +// +// This implementation is optimized for frequent updates, as insertions and deletions only +// require updating 1-2 pointers. However, random access operations are O(n) as they require +// traversing the list. For use cases where writes are rare, a slice-based +// implementation might be more suitable. +// +// The linked list structure is equally efficient for storing both small values (like pointers) +// and larger data structures, as each node maintains a single next-pointer regardless of the +// stored value's size. +// +// Example usage: +// +// list := fifo.New(3) // Create a new list with max size 3 +// list.Append("a") // List: [a] +// list.Append("b") // List: [a b] +// list.Append("c") // List: [a b c] +// list.Append("d") // List: [b c d] (oldest element "a" was removed) +// latest := list.Latest() // Returns "d" +// all := list.Entries() // Returns ["b", "c", "d"] +package fifo + +// node represents a single element in the linked list +type node struct { + value any + next *node +} + +// List represents a fixed-size FIFO list +type List struct { + head *node + tail *node + size int + maxSize int +} + +// New creates a new FIFO list with the specified maximum size +func New(maxSize int) *List { + return &List{ + maxSize: maxSize, + } +} + +// Prepend adds a new entry at the start of the list. If the list exceeds maxSize, +// the last entry is automatically removed. +func (l *List) Prepend(entry any) { + if l.maxSize == 0 { + return + } + + newNode := &node{value: entry} + + if l.head == nil { + l.head = newNode + l.tail = newNode + l.size = 1 + return + } + + newNode.next = l.head + l.head = newNode + + if l.size < l.maxSize { + l.size++ + return + } + + // Remove last element by traversing to second-to-last + if l.size == 1 { + // Special case: if size is 1, just update both pointers + l.head = newNode + l.tail = newNode + newNode.next = nil + return + + } + + // Find second-to-last node + current := l.head + for current.next != l.tail { + current = current.next + } + current.next = nil + l.tail = current + +} + +// Append adds a new entry at the end of the list. If the list exceeds maxSize, +// the first entry is automatically removed. +func (l *List) Append(entry any) { + if l.maxSize == 0 { + return + } + + newNode := &node{value: entry} + + if l.head == nil { + l.head = newNode + l.tail = newNode + l.size = 1 + return + } + + l.tail.next = newNode + l.tail = newNode + + if l.size < l.maxSize { + l.size++ + } else { + l.head = l.head.next + } +} + +// Get returns the entry at the specified index. +// Index 0 is the oldest entry, Size()-1 is the newest. +func (l *List) Get(index int) any { + if index < 0 || index >= l.size { + return nil + } + + current := l.head + for i := 0; i < index; i++ { + current = current.next + } + return current.value +} + +// Size returns the current number of entries in the list +func (l *List) Size() int { + return l.size +} + +// MaxSize returns the maximum size configured for this list +func (l *List) MaxSize() int { + return l.maxSize +} + +// Entries returns all current entries as a slice +func (l *List) Entries() []any { + entries := make([]any, l.size) + current := l.head + for i := 0; i < l.size; i++ { + entries[i] = current.value + current = current.next + } + return entries +} + +// Iterator returns a function that can be used to iterate over the entries +// from oldest to newest. Returns nil when there are no more entries. +func (l *List) Iterator() func() any { + current := l.head + return func() any { + if current == nil { + return nil + } + value := current.value + current = current.next + return value + } +} + +// Latest returns the most recent entry. +// Returns nil if the list is empty. +func (l *List) Latest() any { + if l.tail == nil { + return nil + } + return l.tail.value +} + +// SetMaxSize updates the maximum size of the list. +// If the new maxSize is smaller than the current size, +// the oldest entries are removed to fit the new size. +func (l *List) SetMaxSize(maxSize int) { + if maxSize < 0 { + maxSize = 0 + } + + // If new maxSize is smaller than current size, + // remove oldest entries until we fit + if maxSize < l.size { + // Special case: if new maxSize is 0, clear the list + if maxSize == 0 { + l.head = nil + l.tail = nil + l.size = 0 + } else { + // Keep the newest entries by moving head forward + diff := l.size - maxSize + for i := 0; i < diff; i++ { + l.head = l.head.next + } + l.size = maxSize + } + } + + l.maxSize = maxSize +} + +// Delete removes the element at the specified index. +// Returns true if an element was removed, false if the index was invalid. +func (l *List) Delete(index int) bool { + if index < 0 || index >= l.size { + return false + } + + // Special case: deleting the only element + if l.size == 1 { + l.head = nil + l.tail = nil + l.size = 0 + return true + } + + // Special case: deleting first element + if index == 0 { + l.head = l.head.next + l.size-- + return true + } + + // Find the node before the one to delete + current := l.head + for i := 0; i < index-1; i++ { + current = current.next + } + + // Special case: deleting last element + if index == l.size-1 { + l.tail = current + current.next = nil + } else { + current.next = current.next.next + } + + l.size-- + return true +} diff --git a/examples/gno.land/p/moul/fifo/fifo_test.gno b/examples/gno.land/p/moul/fifo/fifo_test.gno new file mode 100644 index 00000000000..1e3d27509c1 --- /dev/null +++ b/examples/gno.land/p/moul/fifo/fifo_test.gno @@ -0,0 +1,294 @@ +package fifo + +import ( + "testing" + + "gno.land/p/demo/uassert" +) + +func TestNew(t *testing.T) { + l := New(5) + uassert.Equal(t, 5, l.MaxSize()) + uassert.Equal(t, 0, l.Size()) +} + +func TestAppend(t *testing.T) { + l := New(3) + + // Test adding within capacity + l.Append(1) + l.Append(2) + uassert.Equal(t, 2, l.Size()) + uassert.Equal(t, 1, l.Get(0)) + uassert.Equal(t, 2, l.Get(1)) + + // Test overflow behavior + l.Append(3) + l.Append(4) + uassert.Equal(t, 3, l.Size()) + uassert.Equal(t, 2, l.Get(0)) + uassert.Equal(t, 3, l.Get(1)) + uassert.Equal(t, 4, l.Get(2)) +} + +func TestPrepend(t *testing.T) { + l := New(3) + + // Test adding within capacity + l.Prepend(1) + l.Prepend(2) + uassert.Equal(t, 2, l.Size()) + uassert.Equal(t, 2, l.Get(0)) + uassert.Equal(t, 1, l.Get(1)) + + // Test overflow behavior + l.Prepend(3) + l.Prepend(4) + uassert.Equal(t, 3, l.Size()) + uassert.Equal(t, 4, l.Get(0)) + uassert.Equal(t, 3, l.Get(1)) + uassert.Equal(t, 2, l.Get(2)) +} + +func TestGet(t *testing.T) { + l := New(3) + l.Append(1) + l.Append(2) + l.Append(3) + + // Test valid indices + uassert.Equal(t, 1, l.Get(0)) + uassert.Equal(t, 2, l.Get(1)) + uassert.Equal(t, 3, l.Get(2)) + + // Test invalid indices + uassert.True(t, l.Get(-1) == nil) + uassert.True(t, l.Get(3) == nil) +} + +func TestEntries(t *testing.T) { + l := New(3) + l.Append(1) + l.Append(2) + l.Append(3) + + entries := l.Entries() + uassert.Equal(t, 3, len(entries)) + uassert.Equal(t, 1, entries[0]) + uassert.Equal(t, 2, entries[1]) + uassert.Equal(t, 3, entries[2]) +} + +func TestLatest(t *testing.T) { + l := New(5) + + // Test empty list + uassert.True(t, l.Latest() == nil) + + // Test single entry + l.Append(1) + uassert.Equal(t, 1, l.Latest()) + + // Test multiple entries + l.Append(2) + l.Append(3) + uassert.Equal(t, 3, l.Latest()) + + // Test after overflow + l.Append(4) + l.Append(5) + l.Append(6) + uassert.Equal(t, 6, l.Latest()) +} + +func TestIterator(t *testing.T) { + l := New(3) + l.Append(1) + l.Append(2) + l.Append(3) + + iter := l.Iterator() + uassert.Equal(t, 1, iter()) + uassert.Equal(t, 2, iter()) + uassert.Equal(t, 3, iter()) + uassert.True(t, iter() == nil) +} + +func TestMixedOperations(t *testing.T) { + l := New(3) + + // Mix of append and prepend operations + l.Append(1) // [1] + l.Prepend(2) // [2,1] + l.Append(3) // [2,1,3] + l.Prepend(4) // [4,2,1] + + entries := l.Entries() + uassert.Equal(t, 3, len(entries)) + uassert.Equal(t, 4, entries[0]) + uassert.Equal(t, 2, entries[1]) + uassert.Equal(t, 1, entries[2]) +} + +func TestEmptyList(t *testing.T) { + l := New(3) + + // Test operations on empty list + uassert.Equal(t, 0, l.Size()) + uassert.True(t, l.Get(0) == nil) + uassert.Equal(t, 0, len(l.Entries())) + uassert.True(t, l.Latest() == nil) + + iter := l.Iterator() + uassert.True(t, iter() == nil) +} + +func TestEdgeCases(t *testing.T) { + // Test zero-size list + l := New(0) + uassert.Equal(t, 0, l.MaxSize()) + l.Append(1) // Should be no-op + uassert.Equal(t, 0, l.Size()) + + // Test single-element list + l = New(1) + l.Append(1) + l.Append(2) // Should replace 1 + uassert.Equal(t, 1, l.Size()) + uassert.Equal(t, 2, l.Latest()) + + // Test rapid append/prepend alternation + l = New(3) + l.Append(1) // [1] + l.Prepend(2) // [2,1] + l.Append(3) // [2,1,3] + l.Prepend(4) // [4,2,1] + l.Append(5) // [2,1,5] + uassert.Equal(t, 3, l.Size()) + entries := l.Entries() + uassert.Equal(t, 2, entries[0]) + uassert.Equal(t, 1, entries[1]) + uassert.Equal(t, 5, entries[2]) + + // Test nil values + l = New(2) + l.Append(nil) + l.Prepend(nil) + uassert.Equal(t, 2, l.Size()) + uassert.True(t, l.Get(0) == nil) + uassert.True(t, l.Get(1) == nil) + + // Test index bounds + l = New(3) + l.Append(1) + uassert.True(t, l.Get(-1) == nil) + uassert.True(t, l.Get(1) == nil) + + // Test iterator exhaustion + l = New(2) + l.Append(1) + l.Append(2) + iter := l.Iterator() + uassert.Equal(t, 1, iter()) + uassert.Equal(t, 2, iter()) + uassert.True(t, iter() == nil) + uassert.True(t, iter() == nil) + + // Test prepend on full list + l = New(2) + l.Append(1) + l.Append(2) // [1,2] + l.Prepend(3) // [3,1] + uassert.Equal(t, 2, l.Size()) + entries = l.Entries() + uassert.Equal(t, 3, entries[0]) + uassert.Equal(t, 1, entries[1]) +} + +func TestSetMaxSize(t *testing.T) { + l := New(5) + + // Fill the list + l.Append(1) + l.Append(2) + l.Append(3) + l.Append(4) + l.Append(5) + + // Test increasing maxSize + l.SetMaxSize(7) + uassert.Equal(t, 7, l.MaxSize()) + uassert.Equal(t, 5, l.Size()) + + // Test reducing maxSize + l.SetMaxSize(3) + uassert.Equal(t, 3, l.Size()) + entries := l.Entries() + uassert.Equal(t, 3, entries[0]) + uassert.Equal(t, 4, entries[1]) + uassert.Equal(t, 5, entries[2]) + + // Test setting to zero + l.SetMaxSize(0) + uassert.Equal(t, 0, l.Size()) + uassert.True(t, l.head == nil) + uassert.True(t, l.tail == nil) + + // Test negative maxSize + l.SetMaxSize(-1) + uassert.Equal(t, 0, l.MaxSize()) + + // Test setting back to positive + l.SetMaxSize(2) + l.Append(1) + l.Append(2) + l.Append(3) + uassert.Equal(t, 2, l.Size()) + entries = l.Entries() + uassert.Equal(t, 2, entries[0]) + uassert.Equal(t, 3, entries[1]) +} + +func TestDelete(t *testing.T) { + l := New(5) + + // Test delete on empty list + uassert.False(t, l.Delete(0)) + uassert.False(t, l.Delete(-1)) + + // Fill list + l.Append(1) + l.Append(2) + l.Append(3) + l.Append(4) + + // Test invalid indices + uassert.False(t, l.Delete(-1)) + uassert.False(t, l.Delete(4)) + + // Test deleting from middle + uassert.True(t, l.Delete(1)) + uassert.Equal(t, 3, l.Size()) + entries := l.Entries() + uassert.Equal(t, 1, entries[0]) + uassert.Equal(t, 3, entries[1]) + uassert.Equal(t, 4, entries[2]) + + // Test deleting from head + uassert.True(t, l.Delete(0)) + uassert.Equal(t, 2, l.Size()) + entries = l.Entries() + uassert.Equal(t, 3, entries[0]) + uassert.Equal(t, 4, entries[1]) + + // Test deleting from tail + uassert.True(t, l.Delete(1)) + uassert.Equal(t, 1, l.Size()) + uassert.Equal(t, 3, l.Latest()) + + // Test deleting last element + uassert.True(t, l.Delete(0)) + uassert.Equal(t, 0, l.Size()) + uassert.True(t, l.head == nil) + uassert.True(t, l.tail == nil) +} diff --git a/examples/gno.land/p/moul/fifo/gno.mod b/examples/gno.land/p/moul/fifo/gno.mod new file mode 100644 index 00000000000..dccbc39453b --- /dev/null +++ b/examples/gno.land/p/moul/fifo/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/fifo diff --git a/examples/gno.land/p/moul/fp/fp.gno b/examples/gno.land/p/moul/fp/fp.gno index b2811c77d5a..6a8a8a9523c 100644 --- a/examples/gno.land/p/moul/fp/fp.gno +++ b/examples/gno.land/p/moul/fp/fp.gno @@ -1,14 +1,14 @@ // Package fp provides functional programming utilities for Gno, enabling -// transformations, filtering, and other operations on slices of interface{}. +// transformations, filtering, and other operations on slices of any. // // Example of chaining operations: // -// numbers := []interface{}{1, 2, 3, 4, 5, 6} +// numbers := []any{1, 2, 3, 4, 5, 6} // // // Define predicates, mappers and reducers -// isEven := func(v interface{}) bool { return v.(int)%2 == 0 } -// double := func(v interface{}) interface{} { return v.(int) * 2 } -// sum := func(a, b interface{}) interface{} { return a.(int) + b.(int) } +// isEven := func(v any) bool { return v.(int)%2 == 0 } +// double := func(v any) any { return v.(int) * 2 } +// sum := func(a, b any) any { return a.(int) + b.(int) } // // // Chain operations: filter even numbers, double them, then sum // evenNums := Filter(numbers, isEven) // [2, 4, 6] @@ -16,29 +16,29 @@ // result := Reduce(doubled, sum, 0) // 24 // // // Alternative: group by even/odd, then get even numbers -// byMod2 := func(v interface{}) interface{} { return v.(int) % 2 } +// byMod2 := func(v any) any { return v.(int) % 2 } // grouped := GroupBy(numbers, byMod2) // {0: [2,4,6], 1: [1,3,5]} // evens := grouped[0] // [2,4,6] package fp // Mapper is a function type that maps an element to another element. -type Mapper func(interface{}) interface{} +type Mapper func(any) any // Predicate is a function type that evaluates a condition on an element. -type Predicate func(interface{}) bool +type Predicate func(any) bool // Reducer is a function type that reduces two elements to a single value. -type Reducer func(interface{}, interface{}) interface{} +type Reducer func(any, any) any // Filter filters elements from the slice that satisfy the given predicate. // // Example: // -// numbers := []interface{}{-1, 0, 1, 2} -// isPositive := func(v interface{}) bool { return v.(int) > 0 } +// numbers := []any{-1, 0, 1, 2} +// isPositive := func(v any) bool { return v.(int) > 0 } // result := Filter(numbers, isPositive) // [1, 2] -func Filter(values []interface{}, fn Predicate) []interface{} { - result := []interface{}{} +func Filter(values []any, fn Predicate) []any { + result := []any{} for _, v := range values { if fn(v) { result = append(result, v) @@ -51,11 +51,11 @@ func Filter(values []interface{}, fn Predicate) []interface{} { // // Example: // -// numbers := []interface{}{1, 2, 3} -// toString := func(v interface{}) interface{} { return fmt.Sprintf("%d", v) } +// numbers := []any{1, 2, 3} +// toString := func(v any) any { return fmt.Sprintf("%d", v) } // result := Map(numbers, toString) // ["1", "2", "3"] -func Map(values []interface{}, fn Mapper) []interface{} { - result := make([]interface{}, len(values)) +func Map(values []any, fn Mapper) []any { + result := make([]any, len(values)) for i, v := range values { result[i] = fn(v) } @@ -66,10 +66,10 @@ func Map(values []interface{}, fn Mapper) []interface{} { // // Example: // -// numbers := []interface{}{1, 2, 3, 4} -// sum := func(a, b interface{}) interface{} { return a.(int) + b.(int) } +// numbers := []any{1, 2, 3, 4} +// sum := func(a, b any) any { return a.(int) + b.(int) } // result := Reduce(numbers, sum, 0) // 10 -func Reduce(values []interface{}, fn Reducer, initial interface{}) interface{} { +func Reduce(values []any, fn Reducer, initial any) any { acc := initial for _, v := range values { acc = fn(acc, v) @@ -81,19 +81,19 @@ func Reduce(values []interface{}, fn Reducer, initial interface{}) interface{} { // // Example: // -// words := []interface{}{"hello", "world"} -// split := func(v interface{}) interface{} { -// chars := []interface{}{} +// words := []any{"hello", "world"} +// split := func(v any) any { +// chars := []any{} // for _, c := range v.(string) { // chars = append(chars, string(c)) // } // return chars // } // result := FlatMap(words, split) // ["h","e","l","l","o","w","o","r","l","d"] -func FlatMap(values []interface{}, fn Mapper) []interface{} { - result := []interface{}{} +func FlatMap(values []any, fn Mapper) []any { + result := []any{} for _, v := range values { - inner := fn(v).([]interface{}) + inner := fn(v).([]any) result = append(result, inner...) } return result @@ -103,10 +103,10 @@ func FlatMap(values []interface{}, fn Mapper) []interface{} { // // Example: // -// numbers := []interface{}{2, 4, 6, 8} -// isEven := func(v interface{}) bool { return v.(int)%2 == 0 } +// numbers := []any{2, 4, 6, 8} +// isEven := func(v any) bool { return v.(int)%2 == 0 } // result := All(numbers, isEven) // true -func All(values []interface{}, fn Predicate) bool { +func All(values []any, fn Predicate) bool { for _, v := range values { if !fn(v) { return false @@ -119,10 +119,10 @@ func All(values []interface{}, fn Predicate) bool { // // Example: // -// numbers := []interface{}{1, 3, 4, 7} -// isEven := func(v interface{}) bool { return v.(int)%2 == 0 } +// numbers := []any{1, 3, 4, 7} +// isEven := func(v any) bool { return v.(int)%2 == 0 } // result := Any(numbers, isEven) // true (4 is even) -func Any(values []interface{}, fn Predicate) bool { +func Any(values []any, fn Predicate) bool { for _, v := range values { if fn(v) { return true @@ -135,10 +135,10 @@ func Any(values []interface{}, fn Predicate) bool { // // Example: // -// numbers := []interface{}{1, 3, 5, 7} -// isEven := func(v interface{}) bool { return v.(int)%2 == 0 } +// numbers := []any{1, 3, 5, 7} +// isEven := func(v any) bool { return v.(int)%2 == 0 } // result := None(numbers, isEven) // true (no even numbers) -func None(values []interface{}, fn Predicate) bool { +func None(values []any, fn Predicate) bool { for _, v := range values { if fn(v) { return false @@ -151,13 +151,13 @@ func None(values []interface{}, fn Predicate) bool { // // Example: // -// numbers := []interface{}{1, 2, 3, 4, 5} +// numbers := []any{1, 2, 3, 4, 5} // result := Chunk(numbers, 2) // [[1,2], [3,4], [5]] -func Chunk(values []interface{}, size int) [][]interface{} { +func Chunk(values []any, size int) [][]any { if size <= 0 { return nil } - var chunks [][]interface{} + var chunks [][]any for i := 0; i < len(values); i += size { end := i + size if end > len(values) { @@ -172,10 +172,10 @@ func Chunk(values []interface{}, size int) [][]interface{} { // // Example: // -// numbers := []interface{}{1, 2, 3, 4} -// isEven := func(v interface{}) bool { return v.(int)%2 == 0 } +// numbers := []any{1, 2, 3, 4} +// isEven := func(v any) bool { return v.(int)%2 == 0 } // result, found := Find(numbers, isEven) // 2, true -func Find(values []interface{}, fn Predicate) (interface{}, bool) { +func Find(values []any, fn Predicate) (any, bool) { for _, v := range values { if fn(v) { return v, true @@ -188,10 +188,10 @@ func Find(values []interface{}, fn Predicate) (interface{}, bool) { // // Example: // -// numbers := []interface{}{1, 2, 3} +// numbers := []any{1, 2, 3} // result := Reverse(numbers) // [3, 2, 1] -func Reverse(values []interface{}) []interface{} { - result := make([]interface{}, len(values)) +func Reverse(values []any) []any { + result := make([]any, len(values)) for i, v := range values { result[len(values)-1-i] = v } @@ -203,14 +203,14 @@ func Reverse(values []interface{}) []interface{} { // // Example: // -// a := []interface{}{1, 2, 3} -// b := []interface{}{"a", "b", "c"} +// a := []any{1, 2, 3} +// b := []any{"a", "b", "c"} // result := Zip(a, b) // [[1,"a"], [2,"b"], [3,"c"]] -func Zip(a, b []interface{}) [][2]interface{} { +func Zip(a, b []any) [][2]any { length := min(len(a), len(b)) - result := make([][2]interface{}, length) + result := make([][2]any, length) for i := 0; i < length; i++ { - result[i] = [2]interface{}{a[i], b[i]} + result[i] = [2]any{a[i], b[i]} } return result } @@ -219,11 +219,11 @@ func Zip(a, b []interface{}) [][2]interface{} { // // Example: // -// pairs := [][2]interface{}{{1,"a"}, {2,"b"}, {3,"c"}} +// pairs := [][2]any{{1,"a"}, {2,"b"}, {3,"c"}} // numbers, letters := Unzip(pairs) // [1,2,3], ["a","b","c"] -func Unzip(pairs [][2]interface{}) ([]interface{}, []interface{}) { - a := make([]interface{}, len(pairs)) - b := make([]interface{}, len(pairs)) +func Unzip(pairs [][2]any) ([]any, []any) { + a := make([]any, len(pairs)) + b := make([]any, len(pairs)) for i, pair := range pairs { a[i] = pair[0] b[i] = pair[1] @@ -235,11 +235,11 @@ func Unzip(pairs [][2]interface{}) ([]interface{}, []interface{}) { // // Example: // -// numbers := []interface{}{1, 2, 3, 4, 5, 6} -// byMod3 := func(v interface{}) interface{} { return v.(int) % 3 } +// numbers := []any{1, 2, 3, 4, 5, 6} +// byMod3 := func(v any) any { return v.(int) % 3 } // result := GroupBy(numbers, byMod3) // {0: [3,6], 1: [1,4], 2: [2,5]} -func GroupBy(values []interface{}, fn Mapper) map[interface{}][]interface{} { - result := make(map[interface{}][]interface{}) +func GroupBy(values []any, fn Mapper) map[any][]any { + result := make(map[any][]any) for _, v := range values { key := fn(v) result[key] = append(result[key], v) @@ -251,10 +251,10 @@ func GroupBy(values []interface{}, fn Mapper) map[interface{}][]interface{} { // // Example: // -// nested := [][]interface{}{{1,2}, {3,4}, {5}} +// nested := [][]any{{1,2}, {3,4}, {5}} // result := Flatten(nested) // [1,2,3,4,5] -func Flatten(values [][]interface{}) []interface{} { - result := []interface{}{} +func Flatten(values [][]any) []any { + result := []any{} for _, v := range values { result = append(result, v...) } diff --git a/examples/gno.land/p/moul/fp/fp_test.gno b/examples/gno.land/p/moul/fp/fp_test.gno index 00957486fe9..0207efd98e3 100644 --- a/examples/gno.land/p/moul/fp/fp_test.gno +++ b/examples/gno.land/p/moul/fp/fp_test.gno @@ -8,27 +8,27 @@ import ( func TestMap(t *testing.T) { tests := []struct { name string - input []interface{} - fn func(interface{}) interface{} - expected []interface{} + input []any + fn func(any) any + expected []any }{ { name: "multiply numbers by 2", - input: []interface{}{1, 2, 3}, - fn: func(v interface{}) interface{} { return v.(int) * 2 }, - expected: []interface{}{2, 4, 6}, + input: []any{1, 2, 3}, + fn: func(v any) any { return v.(int) * 2 }, + expected: []any{2, 4, 6}, }, { name: "empty slice", - input: []interface{}{}, - fn: func(v interface{}) interface{} { return v.(int) * 2 }, - expected: []interface{}{}, + input: []any{}, + fn: func(v any) any { return v.(int) * 2 }, + expected: []any{}, }, { name: "convert numbers to strings", - input: []interface{}{1, 2, 3}, - fn: func(v interface{}) interface{} { return fmt.Sprintf("%d", v.(int)) }, - expected: []interface{}{"1", "2", "3"}, + input: []any{1, 2, 3}, + fn: func(v any) any { return fmt.Sprintf("%d", v.(int)) }, + expected: []any{"1", "2", "3"}, }, } @@ -45,33 +45,33 @@ func TestMap(t *testing.T) { func TestFilter(t *testing.T) { tests := []struct { name string - input []interface{} - fn func(interface{}) bool - expected []interface{} + input []any + fn func(any) bool + expected []any }{ { name: "filter even numbers", - input: []interface{}{1, 2, 3, 4}, - fn: func(v interface{}) bool { return v.(int)%2 == 0 }, - expected: []interface{}{2, 4}, + input: []any{1, 2, 3, 4}, + fn: func(v any) bool { return v.(int)%2 == 0 }, + expected: []any{2, 4}, }, { name: "empty slice", - input: []interface{}{}, - fn: func(v interface{}) bool { return v.(int)%2 == 0 }, - expected: []interface{}{}, + input: []any{}, + fn: func(v any) bool { return v.(int)%2 == 0 }, + expected: []any{}, }, { name: "no matches", - input: []interface{}{1, 3, 5}, - fn: func(v interface{}) bool { return v.(int)%2 == 0 }, - expected: []interface{}{}, + input: []any{1, 3, 5}, + fn: func(v any) bool { return v.(int)%2 == 0 }, + expected: []any{}, }, { name: "all matches", - input: []interface{}{2, 4, 6}, - fn: func(v interface{}) bool { return v.(int)%2 == 0 }, - expected: []interface{}{2, 4, 6}, + input: []any{2, 4, 6}, + fn: func(v any) bool { return v.(int)%2 == 0 }, + expected: []any{2, 4, 6}, }, } @@ -88,29 +88,29 @@ func TestFilter(t *testing.T) { func TestReduce(t *testing.T) { tests := []struct { name string - input []interface{} - fn func(interface{}, interface{}) interface{} - initial interface{} - expected interface{} + input []any + fn func(any, any) any + initial any + expected any }{ { name: "sum numbers", - input: []interface{}{1, 2, 3}, - fn: func(a, b interface{}) interface{} { return a.(int) + b.(int) }, + input: []any{1, 2, 3}, + fn: func(a, b any) any { return a.(int) + b.(int) }, initial: 0, expected: 6, }, { name: "empty slice", - input: []interface{}{}, - fn: func(a, b interface{}) interface{} { return a.(int) + b.(int) }, + input: []any{}, + fn: func(a, b any) any { return a.(int) + b.(int) }, initial: 0, expected: 0, }, { name: "concatenate strings", - input: []interface{}{"a", "b", "c"}, - fn: func(a, b interface{}) interface{} { return a.(string) + b.(string) }, + input: []any{"a", "b", "c"}, + fn: func(a, b any) any { return a.(string) + b.(string) }, initial: "", expected: "abc", }, @@ -129,60 +129,60 @@ func TestReduce(t *testing.T) { func TestFlatMap(t *testing.T) { tests := []struct { name string - input []interface{} - fn func(interface{}) interface{} - expected []interface{} + input []any + fn func(any) any + expected []any }{ { name: "split words into chars", - input: []interface{}{"go", "fn"}, - fn: func(word interface{}) interface{} { - chars := []interface{}{} + input: []any{"go", "fn"}, + fn: func(word any) any { + chars := []any{} for _, c := range word.(string) { chars = append(chars, string(c)) } return chars }, - expected: []interface{}{"g", "o", "f", "n"}, + expected: []any{"g", "o", "f", "n"}, }, { name: "empty string handling", - input: []interface{}{"", "a", ""}, - fn: func(word interface{}) interface{} { - chars := []interface{}{} + input: []any{"", "a", ""}, + fn: func(word any) any { + chars := []any{} for _, c := range word.(string) { chars = append(chars, string(c)) } return chars }, - expected: []interface{}{"a"}, + expected: []any{"a"}, }, { name: "nil handling", - input: []interface{}{nil, "a", nil}, - fn: func(word interface{}) interface{} { + input: []any{nil, "a", nil}, + fn: func(word any) any { if word == nil { - return []interface{}{} + return []any{} } - return []interface{}{word} + return []any{word} }, - expected: []interface{}{"a"}, + expected: []any{"a"}, }, { name: "empty slice result", - input: []interface{}{"", "", ""}, - fn: func(word interface{}) interface{} { - return []interface{}{} + input: []any{"", "", ""}, + fn: func(word any) any { + return []any{} }, - expected: []interface{}{}, + expected: []any{}, }, { name: "nested array flattening", - input: []interface{}{1, 2, 3}, - fn: func(n interface{}) interface{} { - return []interface{}{n, n} + input: []any{1, 2, 3}, + fn: func(n any) any { + return []any{n, n} }, - expected: []interface{}{1, 1, 2, 2, 3, 3}, + expected: []any{1, 1, 2, 2, 3, 3}, }, } @@ -199,48 +199,48 @@ func TestFlatMap(t *testing.T) { func TestAllAnyNone(t *testing.T) { tests := []struct { name string - input []interface{} - fn func(interface{}) bool + input []any + fn func(any) bool expectedAll bool expectedAny bool expectedNone bool }{ { name: "all even numbers", - input: []interface{}{2, 4, 6, 8}, - fn: func(x interface{}) bool { return x.(int)%2 == 0 }, + input: []any{2, 4, 6, 8}, + fn: func(x any) bool { return x.(int)%2 == 0 }, expectedAll: true, expectedAny: true, expectedNone: false, }, { name: "no even numbers", - input: []interface{}{1, 3, 5, 7}, - fn: func(x interface{}) bool { return x.(int)%2 == 0 }, + input: []any{1, 3, 5, 7}, + fn: func(x any) bool { return x.(int)%2 == 0 }, expectedAll: false, expectedAny: false, expectedNone: true, }, { name: "mixed even/odd numbers", - input: []interface{}{1, 2, 3, 4}, - fn: func(x interface{}) bool { return x.(int)%2 == 0 }, + input: []any{1, 2, 3, 4}, + fn: func(x any) bool { return x.(int)%2 == 0 }, expectedAll: false, expectedAny: true, expectedNone: false, }, { name: "empty slice", - input: []interface{}{}, - fn: func(x interface{}) bool { return x.(int)%2 == 0 }, + input: []any{}, + fn: func(x any) bool { return x.(int)%2 == 0 }, expectedAll: true, // vacuously true expectedAny: false, // vacuously false expectedNone: true, // vacuously true }, { name: "nil predicate handling", - input: []interface{}{nil, nil, nil}, - fn: func(x interface{}) bool { return x == nil }, + input: []any{nil, nil, nil}, + fn: func(x any) bool { return x == nil }, expectedAll: true, expectedAny: true, expectedNone: false, @@ -270,33 +270,33 @@ func TestAllAnyNone(t *testing.T) { func TestChunk(t *testing.T) { tests := []struct { name string - input []interface{} + input []any size int - expected [][]interface{} + expected [][]any }{ { name: "normal chunks", - input: []interface{}{1, 2, 3, 4, 5}, + input: []any{1, 2, 3, 4, 5}, size: 2, - expected: [][]interface{}{{1, 2}, {3, 4}, {5}}, + expected: [][]any{{1, 2}, {3, 4}, {5}}, }, { name: "empty slice", - input: []interface{}{}, + input: []any{}, size: 2, - expected: [][]interface{}{}, + expected: [][]any{}, }, { name: "chunk size equals length", - input: []interface{}{1, 2, 3}, + input: []any{1, 2, 3}, size: 3, - expected: [][]interface{}{{1, 2, 3}}, + expected: [][]any{{1, 2, 3}}, }, { name: "chunk size larger than length", - input: []interface{}{1, 2}, + input: []any{1, 2}, size: 3, - expected: [][]interface{}{{1, 2}}, + expected: [][]any{{1, 2}}, }, } @@ -313,29 +313,29 @@ func TestChunk(t *testing.T) { func TestFind(t *testing.T) { tests := []struct { name string - input []interface{} - fn func(interface{}) bool - expected interface{} + input []any + fn func(any) bool + expected any shouldFound bool }{ { name: "find first number greater than 2", - input: []interface{}{1, 2, 3, 4}, - fn: func(v interface{}) bool { return v.(int) > 2 }, + input: []any{1, 2, 3, 4}, + fn: func(v any) bool { return v.(int) > 2 }, expected: 3, shouldFound: true, }, { name: "empty slice", - input: []interface{}{}, - fn: func(v interface{}) bool { return v.(int) > 2 }, + input: []any{}, + fn: func(v any) bool { return v.(int) > 2 }, expected: nil, shouldFound: false, }, { name: "no match", - input: []interface{}{1, 2}, - fn: func(v interface{}) bool { return v.(int) > 10 }, + input: []any{1, 2}, + fn: func(v any) bool { return v.(int) > 10 }, expected: nil, shouldFound: false, }, @@ -357,28 +357,28 @@ func TestFind(t *testing.T) { func TestReverse(t *testing.T) { tests := []struct { name string - input []interface{} - expected []interface{} + input []any + expected []any }{ { name: "normal sequence", - input: []interface{}{1, 2, 3, 4}, - expected: []interface{}{4, 3, 2, 1}, + input: []any{1, 2, 3, 4}, + expected: []any{4, 3, 2, 1}, }, { name: "empty slice", - input: []interface{}{}, - expected: []interface{}{}, + input: []any{}, + expected: []any{}, }, { name: "single element", - input: []interface{}{1}, - expected: []interface{}{1}, + input: []any{1}, + expected: []any{1}, }, { name: "mixed types", - input: []interface{}{1, "a", true, 2.5}, - expected: []interface{}{2.5, true, "a", 1}, + input: []any{1, "a", true, 2.5}, + expected: []any{2.5, true, "a", 1}, }, } @@ -395,51 +395,51 @@ func TestReverse(t *testing.T) { func TestZipUnzip(t *testing.T) { tests := []struct { name string - a []interface{} - b []interface{} - expectedZip [][2]interface{} - expectedA []interface{} - expectedB []interface{} + a []any + b []any + expectedZip [][2]any + expectedA []any + expectedB []any }{ { name: "normal case", - a: []interface{}{1, 2, 3}, - b: []interface{}{"a", "b", "c"}, - expectedZip: [][2]interface{}{{1, "a"}, {2, "b"}, {3, "c"}}, - expectedA: []interface{}{1, 2, 3}, - expectedB: []interface{}{"a", "b", "c"}, + a: []any{1, 2, 3}, + b: []any{"a", "b", "c"}, + expectedZip: [][2]any{{1, "a"}, {2, "b"}, {3, "c"}}, + expectedA: []any{1, 2, 3}, + expectedB: []any{"a", "b", "c"}, }, { name: "empty slices", - a: []interface{}{}, - b: []interface{}{}, - expectedZip: [][2]interface{}{}, - expectedA: []interface{}{}, - expectedB: []interface{}{}, + a: []any{}, + b: []any{}, + expectedZip: [][2]any{}, + expectedA: []any{}, + expectedB: []any{}, }, { name: "different lengths - a shorter", - a: []interface{}{1, 2}, - b: []interface{}{"a", "b", "c"}, - expectedZip: [][2]interface{}{{1, "a"}, {2, "b"}}, - expectedA: []interface{}{1, 2}, - expectedB: []interface{}{"a", "b"}, + a: []any{1, 2}, + b: []any{"a", "b", "c"}, + expectedZip: [][2]any{{1, "a"}, {2, "b"}}, + expectedA: []any{1, 2}, + expectedB: []any{"a", "b"}, }, { name: "different lengths - b shorter", - a: []interface{}{1, 2, 3}, - b: []interface{}{"a"}, - expectedZip: [][2]interface{}{{1, "a"}}, - expectedA: []interface{}{1}, - expectedB: []interface{}{"a"}, + a: []any{1, 2, 3}, + b: []any{"a"}, + expectedZip: [][2]any{{1, "a"}}, + expectedA: []any{1}, + expectedB: []any{"a"}, }, { name: "mixed types", - a: []interface{}{1, true, "x"}, - b: []interface{}{2.5, false, "y"}, - expectedZip: [][2]interface{}{{1, 2.5}, {true, false}, {"x", "y"}}, - expectedA: []interface{}{1, true, "x"}, - expectedB: []interface{}{2.5, false, "y"}, + a: []any{1, true, "x"}, + b: []any{2.5, false, "y"}, + expectedZip: [][2]any{{1, 2.5}, {true, false}, {"x", "y"}}, + expectedA: []any{1, true, "x"}, + expectedB: []any{2.5, false, "y"}, }, } @@ -469,37 +469,37 @@ func TestZipUnzip(t *testing.T) { func TestGroupBy(t *testing.T) { tests := []struct { name string - input []interface{} - fn func(interface{}) interface{} - expected map[interface{}][]interface{} + input []any + fn func(any) any + expected map[any][]any }{ { name: "group by even/odd", - input: []interface{}{1, 2, 3, 4, 5, 6}, - fn: func(v interface{}) interface{} { return v.(int) % 2 }, - expected: map[interface{}][]interface{}{ + input: []any{1, 2, 3, 4, 5, 6}, + fn: func(v any) any { return v.(int) % 2 }, + expected: map[any][]any{ 0: {2, 4, 6}, 1: {1, 3, 5}, }, }, { name: "empty slice", - input: []interface{}{}, - fn: func(v interface{}) interface{} { return v.(int) % 2 }, - expected: map[interface{}][]interface{}{}, + input: []any{}, + fn: func(v any) any { return v.(int) % 2 }, + expected: map[any][]any{}, }, { name: "single group", - input: []interface{}{2, 4, 6}, - fn: func(v interface{}) interface{} { return v.(int) % 2 }, - expected: map[interface{}][]interface{}{ + input: []any{2, 4, 6}, + fn: func(v any) any { return v.(int) % 2 }, + expected: map[any][]any{ 0: {2, 4, 6}, }, }, { name: "group by type", - input: []interface{}{1, "a", 2, "b", true}, - fn: func(v interface{}) interface{} { + input: []any{1, "a", 2, "b", true}, + fn: func(v any) any { switch v.(type) { case int: return "int" @@ -509,7 +509,7 @@ func TestGroupBy(t *testing.T) { return "other" } }, - expected: map[interface{}][]interface{}{ + expected: map[any][]any{ "int": {1, 2}, "string": {"a", "b"}, "other": {true}, @@ -535,33 +535,33 @@ func TestGroupBy(t *testing.T) { func TestFlatten(t *testing.T) { tests := []struct { name string - input [][]interface{} - expected []interface{} + input [][]any + expected []any }{ { name: "normal nested slices", - input: [][]interface{}{{1, 2}, {3, 4}, {5}}, - expected: []interface{}{1, 2, 3, 4, 5}, + input: [][]any{{1, 2}, {3, 4}, {5}}, + expected: []any{1, 2, 3, 4, 5}, }, { name: "empty outer slice", - input: [][]interface{}{}, - expected: []interface{}{}, + input: [][]any{}, + expected: []any{}, }, { name: "empty inner slices", - input: [][]interface{}{{}, {}, {}}, - expected: []interface{}{}, + input: [][]any{{}, {}, {}}, + expected: []any{}, }, { name: "mixed types", - input: [][]interface{}{{1, "a"}, {true, 2.5}, {nil}}, - expected: []interface{}{1, "a", true, 2.5, nil}, + input: [][]any{{1, "a"}, {true, 2.5}, {nil}}, + expected: []any{1, "a", true, 2.5, nil}, }, { name: "single element slices", - input: [][]interface{}{{1}, {2}, {3}}, - expected: []interface{}{1, 2, 3}, + input: [][]any{{1}, {2}, {3}}, + expected: []any{1, 2, 3}, }, } @@ -578,43 +578,43 @@ func TestFlatten(t *testing.T) { func TestContains(t *testing.T) { tests := []struct { name string - slice []interface{} - item interface{} + slice []any + item any expected bool }{ { name: "contains integer", - slice: []interface{}{1, 2, 3}, + slice: []any{1, 2, 3}, item: 2, expected: true, }, { name: "does not contain integer", - slice: []interface{}{1, 2, 3}, + slice: []any{1, 2, 3}, item: 4, expected: false, }, { name: "contains string", - slice: []interface{}{"a", "b", "c"}, + slice: []any{"a", "b", "c"}, item: "b", expected: true, }, { name: "empty slice", - slice: []interface{}{}, + slice: []any{}, item: 1, expected: false, }, { name: "contains nil", - slice: []interface{}{1, nil, 3}, + slice: []any{1, nil, 3}, item: nil, expected: true, }, { name: "mixed types", - slice: []interface{}{1, "a", true}, + slice: []any{1, "a", true}, item: true, expected: true, }, @@ -631,7 +631,7 @@ func TestContains(t *testing.T) { } // Helper function for testing -func contains(slice []interface{}, item interface{}) bool { +func contains(slice []any, item any) bool { for _, v := range slice { if v == item { return true @@ -641,7 +641,7 @@ func contains(slice []interface{}, item interface{}) bool { } // Helper functions for comparing slices -func equalSlices(a, b []interface{}) bool { +func equalSlices(a, b []any) bool { if len(a) != len(b) { return false } @@ -653,7 +653,7 @@ func equalSlices(a, b []interface{}) bool { return true } -func equalNestedSlices(a, b [][]interface{}) bool { +func equalNestedSlices(a, b [][]any) bool { if len(a) != len(b) { return false } diff --git a/examples/gno.land/p/moul/helplink/helplink_test.gno b/examples/gno.land/p/moul/helplink/helplink_test.gno index 29cfd02eb67..440001b94ce 100644 --- a/examples/gno.land/p/moul/helplink/helplink_test.gno +++ b/examples/gno.land/p/moul/helplink/helplink_test.gno @@ -18,7 +18,7 @@ func TestFunc(t *testing.T) { {"Realm Example", "foo", []string{"bar", "1", "baz", "2"}, "[Realm Example](/r/lorem/ipsum$help&func=foo&bar=1&baz=2)", "gno.land/r/lorem/ipsum"}, {"Single Arg", "testFunc", []string{"key", "value"}, "[Single Arg]($help&func=testFunc&key=value)", ""}, {"No Args", "noArgsFunc", []string{}, "[No Args]($help&func=noArgsFunc)", ""}, - {"Odd Args", "oddArgsFunc", []string{"key"}, "[Odd Args]($help&func=oddArgsFunc)", ""}, + {"Odd Args", "oddArgsFunc", []string{"key"}, "[Odd Args]($help&func=oddArgsFunc&error=odd+number+of+arguments)", ""}, } for _, tt := range tests { @@ -39,15 +39,15 @@ func TestFuncURL(t *testing.T) { {"foo", []string{"bar", "1", "baz", "2"}, "$help&func=foo&bar=1&baz=2", ""}, {"testFunc", []string{"key", "value"}, "$help&func=testFunc&key=value", ""}, {"noArgsFunc", []string{}, "$help&func=noArgsFunc", ""}, - {"oddArgsFunc", []string{"key"}, "$help&func=oddArgsFunc", ""}, + {"oddArgsFunc", []string{"key"}, "$help&func=oddArgsFunc&error=odd+number+of+arguments", ""}, {"foo", []string{"bar", "1", "baz", "2"}, "/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.land/r/lorem/ipsum"}, {"testFunc", []string{"key", "value"}, "/r/lorem/ipsum$help&func=testFunc&key=value", "gno.land/r/lorem/ipsum"}, {"noArgsFunc", []string{}, "/r/lorem/ipsum$help&func=noArgsFunc", "gno.land/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum$help&func=oddArgsFunc", "gno.land/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum$help&func=oddArgsFunc&error=odd+number+of+arguments", "gno.land/r/lorem/ipsum"}, {"foo", []string{"bar", "1", "baz", "2"}, "https://gno.world/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.world/r/lorem/ipsum"}, {"testFunc", []string{"key", "value"}, "https://gno.world/r/lorem/ipsum$help&func=testFunc&key=value", "gno.world/r/lorem/ipsum"}, {"noArgsFunc", []string{}, "https://gno.world/r/lorem/ipsum$help&func=noArgsFunc", "gno.world/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum$help&func=oddArgsFunc", "gno.world/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum$help&func=oddArgsFunc&error=odd+number+of+arguments", "gno.world/r/lorem/ipsum"}, } for _, tt := range tests { diff --git a/examples/gno.land/p/moul/md/md_test.gno b/examples/gno.land/p/moul/md/md_test.gno index 144ae58d918..3e8e70efaf6 100644 --- a/examples/gno.land/p/moul/md/md_test.gno +++ b/examples/gno.land/p/moul/md/md_test.gno @@ -1,4 +1,4 @@ -package md +package md_test import ( "testing" diff --git a/examples/gno.land/p/moul/memo/memo.gno b/examples/gno.land/p/moul/memo/memo.gno index e31f13aab15..b6fa54de55d 100644 --- a/examples/gno.land/p/moul/memo/memo.gno +++ b/examples/gno.land/p/moul/memo/memo.gno @@ -21,13 +21,13 @@ // m := memo.New() // // // Cache expensive computation -// result := m.Memoize("key", func() interface{} { +// result := m.Memoize("key", func() any { // // expensive operation // return "computed-value" // }) // // // Subsequent calls with same key return cached result -// result = m.Memoize("key", func() interface{} { +// result = m.Memoize("key", func() any { // // function won't be called, cached value is returned // return "computed-value" // }) @@ -44,13 +44,13 @@ // // Cache value with timestamp // result := m.MemoizeWithValidator( // "key", -// func() interface{} { +// func() any { // return TimestampedValue{ // Value: "data", // Timestamp: time.Now(), // } // }, -// func(cached interface{}) bool { +// func(cached any) bool { // // Validate that the cached value is not older than 1 hour // if tv, ok := cached.(TimestampedValue); ok { // return time.Since(tv.Timestamp) < time.Hour @@ -67,8 +67,8 @@ import ( // Record implements the btree.Record interface for our cache entries type cacheEntry struct { - key interface{} - value interface{} + key any + value any } // Less implements btree.Record interface @@ -92,7 +92,7 @@ func New() *Memoizer { } // Memoize ensures the result of the given function is cached for the specified key. -func (m *Memoizer) Memoize(key interface{}, fn func() interface{}) interface{} { +func (m *Memoizer) Memoize(key any, fn func() any) any { entry := cacheEntry{key: key} if found := m.cache.Get(entry); found != nil { return found.(cacheEntry).value @@ -104,7 +104,7 @@ func (m *Memoizer) Memoize(key interface{}, fn func() interface{}) interface{} { } // MemoizeWithValidator ensures the result is cached and valid according to the validator function. -func (m *Memoizer) MemoizeWithValidator(key interface{}, fn func() interface{}, isValid func(interface{}) bool) interface{} { +func (m *Memoizer) MemoizeWithValidator(key any, fn func() any, isValid func(any) bool) any { entry := cacheEntry{key: key} if found := m.cache.Get(entry); found != nil { cachedEntry := found.(cacheEntry) @@ -119,7 +119,7 @@ func (m *Memoizer) MemoizeWithValidator(key interface{}, fn func() interface{}, } // Invalidate removes the cached value for the specified key. -func (m *Memoizer) Invalidate(key interface{}) { +func (m *Memoizer) Invalidate(key any) { m.cache.Delete(cacheEntry{key: key}) } diff --git a/examples/gno.land/p/moul/memo/memo_test.gno b/examples/gno.land/p/moul/memo/memo_test.gno index 44dde5df640..3c7be419da7 100644 --- a/examples/gno.land/p/moul/memo/memo_test.gno +++ b/examples/gno.land/p/moul/memo/memo_test.gno @@ -7,7 +7,7 @@ import ( ) type timestampedValue struct { - value interface{} + value any timestamp time.Time } @@ -20,8 +20,8 @@ type complexKey struct { func TestMemoize(t *testing.T) { tests := []struct { name string - key interface{} - value interface{} + key any + value any callCount *int }{ { @@ -51,7 +51,7 @@ func TestMemoize(t *testing.T) { t.Errorf("Initial size = %d, want 0", m.Size()) } - fn := func() interface{} { + fn := func() any { *tt.callCount++ return tt.value } @@ -86,8 +86,8 @@ func TestMemoize(t *testing.T) { func TestMemoizeWithValidator(t *testing.T) { tests := []struct { name string - key interface{} - value interface{} + key any + value any validDuration time.Duration waitDuration time.Duration expectedCalls int @@ -118,7 +118,7 @@ func TestMemoizeWithValidator(t *testing.T) { m := New() callCount := 0 - fn := func() interface{} { + fn := func() any { callCount++ return timestampedValue{ value: tt.value, @@ -126,7 +126,7 @@ func TestMemoizeWithValidator(t *testing.T) { } } - isValid := func(cached interface{}) bool { + isValid := func(cached any) bool { if tv, ok := cached.(timestampedValue); ok { return time.Since(tv.timestamp) < tt.validDuration } @@ -158,8 +158,8 @@ func TestMemoizeWithValidator(t *testing.T) { func TestInvalidate(t *testing.T) { tests := []struct { name string - key interface{} - value interface{} + key any + value any callCount *int }{ { @@ -179,7 +179,7 @@ func TestInvalidate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := New() - fn := func() interface{} { + fn := func() any { *tt.callCount++ return tt.value } @@ -215,7 +215,7 @@ func TestClear(t *testing.T) { m := New() callCount := 0 - fn := func() interface{} { + fn := func() any { callCount++ return "value" } @@ -257,7 +257,7 @@ func TestSize(t *testing.T) { } callCount := 0 - fn := func() interface{} { + fn := func() any { callCount++ return "value" } @@ -295,32 +295,32 @@ func TestSize(t *testing.T) { func TestMemoizeWithDifferentKeyTypes(t *testing.T) { tests := []struct { name string - keys []interface{} // Now an array of keys - values []string // Corresponding values + keys []any // Now an array of keys + values []string // Corresponding values callCount *int }{ { name: "integer keys", - keys: []interface{}{42, 43}, + keys: []any{42, 43}, values: []string{"value-for-42", "value-for-43"}, callCount: new(int), }, { name: "float keys", - keys: []interface{}{3.14, 2.718}, + keys: []any{3.14, 2.718}, values: []string{"value-for-pi", "value-for-e"}, callCount: new(int), }, { name: "bool keys", - keys: []interface{}{true, false}, + keys: []any{true, false}, values: []string{"value-for-true", "value-for-false"}, callCount: new(int), }, /* { name: "struct keys", - keys: []interface{}{ + keys: []any{ complexKey{ID: 1, Name: "test1"}, complexKey{ID: 2, Name: "test2"}, }, @@ -329,7 +329,7 @@ func TestMemoizeWithDifferentKeyTypes(t *testing.T) { }, { name: "nil and empty interface keys", - keys: []interface{}{nil, interface{}(nil)}, + keys: []any{nil, any(nil)}, values: []string{"value-for-nil", "value-for-empty-interface"}, callCount: new(int), }, @@ -343,7 +343,7 @@ func TestMemoizeWithDifferentKeyTypes(t *testing.T) { // Test both keys for i, key := range tt.keys { value := tt.values[i] - fn := func() interface{} { + fn := func() any { *tt.callCount++ return value } @@ -366,7 +366,7 @@ func TestMemoizeWithDifferentKeyTypes(t *testing.T) { // Second call for each key should use cache for i, key := range tt.keys { initialCount := *tt.callCount - result := m.Memoize(key, func() interface{} { + result := m.Memoize(key, func() any { *tt.callCount++ return "should-not-be-called" }) @@ -395,7 +395,7 @@ func TestMultipleKeyTypes(t *testing.T) { callCount := 0 // Insert different key types simultaneously (two of each type) - keys := []interface{}{ + keys := []any{ 42, 43, // ints "string-key1", "string-key2", // strings 3.14, 2.718, // floats @@ -404,7 +404,7 @@ func TestMultipleKeyTypes(t *testing.T) { for i, key := range keys { value := i - m.Memoize(key, func() interface{} { + m.Memoize(key, func() any { callCount++ return value }) @@ -418,7 +418,7 @@ func TestMultipleKeyTypes(t *testing.T) { // Verify all values are cached correctly for i, key := range keys { initialCount := callCount - result := m.Memoize(key, func() interface{} { + result := m.Memoize(key, func() any { callCount++ return -1 // Should never be returned if cache works }) diff --git a/examples/gno.land/p/moul/once/gno.mod b/examples/gno.land/p/moul/once/gno.mod new file mode 100644 index 00000000000..29afe6841b2 --- /dev/null +++ b/examples/gno.land/p/moul/once/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/once diff --git a/examples/gno.land/p/moul/once/once.gno b/examples/gno.land/p/moul/once/once.gno new file mode 100644 index 00000000000..171ba511dc2 --- /dev/null +++ b/examples/gno.land/p/moul/once/once.gno @@ -0,0 +1,100 @@ +// Package once provides utilities for one-time execution patterns. +// It extends the concept of sync.Once with error handling and panic options. +package once + +import ( + "errors" +) + +// Once represents a one-time execution guard +type Once struct { + done bool + err error + paniced bool + value any // stores the result of the execution +} + +// New creates a new Once instance +func New() *Once { + return &Once{} +} + +// Do executes fn only once and returns nil on subsequent calls +func (o *Once) Do(fn func()) { + if o.done { + return + } + defer func() { o.done = true }() + fn() +} + +// DoErr executes fn only once and returns the same error on subsequent calls +func (o *Once) DoErr(fn func() error) error { + if o.done { + return o.err + } + defer func() { o.done = true }() + o.err = fn() + return o.err +} + +// DoOrPanic executes fn only once and panics on subsequent calls +func (o *Once) DoOrPanic(fn func()) { + if o.done { + panic("once: multiple execution attempted") + } + defer func() { o.done = true }() + fn() +} + +// DoValue executes fn only once and returns its value, subsequent calls return the cached value +func (o *Once) DoValue(fn func() any) any { + if o.done { + return o.value + } + defer func() { o.done = true }() + o.value = fn() + return o.value +} + +// DoValueErr executes fn only once and returns its value and error +// Subsequent calls return the cached value and error +func (o *Once) DoValueErr(fn func() (any, error)) (any, error) { + if o.done { + return o.value, o.err + } + defer func() { o.done = true }() + o.value, o.err = fn() + return o.value, o.err +} + +// Reset resets the Once instance to its initial state +// This is mainly useful for testing purposes +func (o *Once) Reset() { + o.done = false + o.err = nil + o.paniced = false + o.value = nil +} + +// IsDone returns whether the Once has been executed +func (o *Once) IsDone() bool { + return o.done +} + +// Error returns the error from the last execution if any +func (o *Once) Error() error { + return o.err +} + +var ( + ErrNotExecuted = errors.New("once: not executed yet") +) + +// Value returns the stored value and an error if not executed yet +func (o *Once) Value() (any, error) { + if !o.done { + return nil, ErrNotExecuted + } + return o.value, nil +} diff --git a/examples/gno.land/p/moul/once/once_test.gno b/examples/gno.land/p/moul/once/once_test.gno new file mode 100644 index 00000000000..7cd2690f81a --- /dev/null +++ b/examples/gno.land/p/moul/once/once_test.gno @@ -0,0 +1,231 @@ +package once + +import ( + "errors" + "testing" +) + +func TestOnce_Do(t *testing.T) { + counter := 0 + once := New() + + increment := func() { + counter++ + } + + // First call should execute + once.Do(increment) + if counter != 1 { + t.Errorf("expected counter to be 1, got %d", counter) + } + + // Second call should not execute + once.Do(increment) + if counter != 1 { + t.Errorf("expected counter to still be 1, got %d", counter) + } +} + +func TestOnce_DoErr(t *testing.T) { + once := New() + expectedErr := errors.New("test error") + + fn := func() error { + return expectedErr + } + + // First call should return error + if err := once.DoErr(fn); err != expectedErr { + t.Errorf("expected error %v, got %v", expectedErr, err) + } + + // Second call should return same error + if err := once.DoErr(fn); err != expectedErr { + t.Errorf("expected error %v, got %v", expectedErr, err) + } +} + +func TestOnce_DoOrPanic(t *testing.T) { + once := New() + executed := false + + fn := func() { + executed = true + } + + // First call should execute + once.DoOrPanic(fn) + if !executed { + t.Error("function should have executed") + } + + // Second call should panic + defer func() { + if r := recover(); r == nil { + t.Error("expected panic on second execution") + } + }() + once.DoOrPanic(fn) +} + +func TestOnce_DoValue(t *testing.T) { + once := New() + expected := "test value" + counter := 0 + + fn := func() any { + counter++ + return expected + } + + // First call should return value + if result := once.DoValue(fn); result != expected { + t.Errorf("expected %v, got %v", expected, result) + } + + // Second call should return cached value + if result := once.DoValue(fn); result != expected { + t.Errorf("expected %v, got %v", expected, result) + } + + if counter != 1 { + t.Errorf("function should have executed only once, got %d executions", counter) + } +} + +func TestOnce_DoValueErr(t *testing.T) { + once := New() + expectedVal := "test value" + expectedErr := errors.New("test error") + counter := 0 + + fn := func() (any, error) { + counter++ + return expectedVal, expectedErr + } + + // First call should return value and error + val, err := once.DoValueErr(fn) + if val != expectedVal || err != expectedErr { + t.Errorf("expected (%v, %v), got (%v, %v)", expectedVal, expectedErr, val, err) + } + + // Second call should return cached value and error + val, err = once.DoValueErr(fn) + if val != expectedVal || err != expectedErr { + t.Errorf("expected (%v, %v), got (%v, %v)", expectedVal, expectedErr, val, err) + } + + if counter != 1 { + t.Errorf("function should have executed only once, got %d executions", counter) + } +} + +func TestOnce_Reset(t *testing.T) { + once := New() + counter := 0 + + fn := func() { + counter++ + } + + once.Do(fn) + if counter != 1 { + t.Errorf("expected counter to be 1, got %d", counter) + } + + once.Reset() + once.Do(fn) + if counter != 2 { + t.Errorf("expected counter to be 2 after reset, got %d", counter) + } +} + +func TestOnce_IsDone(t *testing.T) { + once := New() + + if once.IsDone() { + t.Error("new Once instance should not be done") + } + + once.Do(func() {}) + + if !once.IsDone() { + t.Error("Once instance should be done after execution") + } +} + +func TestOnce_Error(t *testing.T) { + once := New() + expectedErr := errors.New("test error") + + if err := once.Error(); err != nil { + t.Errorf("expected nil error, got %v", err) + } + + once.DoErr(func() error { + return expectedErr + }) + + if err := once.Error(); err != expectedErr { + t.Errorf("expected error %v, got %v", expectedErr, err) + } +} + +func TestOnce_Value(t *testing.T) { + once := New() + + // Test unexecuted state + val, err := once.Value() + if err != ErrNotExecuted { + t.Errorf("expected ErrNotExecuted, got %v", err) + } + if val != nil { + t.Errorf("expected nil value, got %v", val) + } + + // Test after execution + expected := "test value" + once.DoValue(func() any { + return expected + }) + + val, err = once.Value() + if err != nil { + t.Errorf("expected nil error, got %v", err) + } + if val != expected { + t.Errorf("expected value %v, got %v", expected, val) + } +} + +func TestOnce_DoValueErr_Panic_MarkedDone(t *testing.T) { + once := New() + count := 0 + fn := func() (any, error) { + count++ + panic("panic") + } + var r any + func() { + defer func() { r = recover() }() + once.DoValueErr(fn) + }() + if r == nil { + t.Error("expected panic on first call") + } + if !once.IsDone() { + t.Error("expected once to be marked as done after panic") + } + r = nil + func() { + defer func() { r = recover() }() + once.DoValueErr(fn) + }() + if r != nil { + t.Error("expected no panic on subsequent call") + } + if count != 1 { + t.Errorf("expected count to be 1, got %d", count) + } +} diff --git a/examples/gno.land/p/moul/txlink/txlink.gno b/examples/gno.land/p/moul/txlink/txlink.gno index 65edda6911e..8f753b4546d 100644 --- a/examples/gno.land/p/moul/txlink/txlink.gno +++ b/examples/gno.land/p/moul/txlink/txlink.gno @@ -15,6 +15,7 @@ package txlink import ( + "net/url" "std" "strings" ) @@ -51,24 +52,26 @@ func (r Realm) prefix() string { // Call returns a URL for the specified function with optional key-value // arguments. func (r Realm) Call(fn string, args ...string) string { - // Start with the base query - url := r.prefix() + "$help&func=" + fn + if len(args) == 0 { + return r.prefix() + "$help&func=" + fn + } + + // Create url.Values to properly encode parameters. + // But manage &func=fn as a special case to keep it as the first argument. + values := url.Values{} // Check if args length is even if len(args)%2 != 0 { - // If not even, we can choose to handle the error here. - // For example, we can just return the URL without appending - // more args. - return url - } - - // Append key-value pairs to the URL - for i := 0; i < len(args); i += 2 { - key := args[i] - value := args[i+1] - // XXX: escape keys and args - url += "&" + key + "=" + value + values.Add("error", "odd number of arguments") + } else { + // Add key-value pairs to values + for i := 0; i < len(args); i += 2 { + key := args[i] + value := args[i+1] + values.Add(key, value) + } } - return url + // Build the base URL and append encoded query parameters + return r.prefix() + "$help&func=" + fn + "&" + values.Encode() } diff --git a/examples/gno.land/p/moul/txlink/txlink_test.gno b/examples/gno.land/p/moul/txlink/txlink_test.gno index 61b532270d4..1da396b27a3 100644 --- a/examples/gno.land/p/moul/txlink/txlink_test.gno +++ b/examples/gno.land/p/moul/txlink/txlink_test.gno @@ -16,19 +16,22 @@ func TestCall(t *testing.T) { {"foo", []string{"bar", "1", "baz", "2"}, "$help&func=foo&bar=1&baz=2", ""}, {"testFunc", []string{"key", "value"}, "$help&func=testFunc&key=value", ""}, {"noArgsFunc", []string{}, "$help&func=noArgsFunc", ""}, - {"oddArgsFunc", []string{"key"}, "$help&func=oddArgsFunc", ""}, + {"oddArgsFunc", []string{"key"}, "$help&func=oddArgsFunc&error=odd+number+of+arguments", ""}, {"foo", []string{"bar", "1", "baz", "2"}, "/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.land/r/lorem/ipsum"}, {"testFunc", []string{"key", "value"}, "/r/lorem/ipsum$help&func=testFunc&key=value", "gno.land/r/lorem/ipsum"}, {"noArgsFunc", []string{}, "/r/lorem/ipsum$help&func=noArgsFunc", "gno.land/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum$help&func=oddArgsFunc", "gno.land/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "/r/lorem/ipsum$help&func=oddArgsFunc&error=odd+number+of+arguments", "gno.land/r/lorem/ipsum"}, {"foo", []string{"bar", "1", "baz", "2"}, "https://gno.world/r/lorem/ipsum$help&func=foo&bar=1&baz=2", "gno.world/r/lorem/ipsum"}, {"testFunc", []string{"key", "value"}, "https://gno.world/r/lorem/ipsum$help&func=testFunc&key=value", "gno.world/r/lorem/ipsum"}, {"noArgsFunc", []string{}, "https://gno.world/r/lorem/ipsum$help&func=noArgsFunc", "gno.world/r/lorem/ipsum"}, - {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum$help&func=oddArgsFunc", "gno.world/r/lorem/ipsum"}, + {"oddArgsFunc", []string{"key"}, "https://gno.world/r/lorem/ipsum$help&func=oddArgsFunc&error=odd+number+of+arguments", "gno.world/r/lorem/ipsum"}, + {"test", []string{"key", "hello world"}, "$help&func=test&key=hello+world", ""}, + {"test", []string{"key", "a&b=c"}, "$help&func=test&key=a%26b%3Dc", ""}, + {"test", []string{"key", ""}, "$help&func=test&key=", ""}, } for _, tt := range tests { - title := tt.fn + title := string(tt.realm) + "_" + tt.fn t.Run(title, func(t *testing.T) { got := tt.realm.Call(tt.fn, tt.args...) urequire.Equal(t, tt.want, got) diff --git a/examples/gno.land/p/moul/typeutil/typeutil.gno b/examples/gno.land/p/moul/typeutil/typeutil.gno index 1fa79b94549..4babd777570 100644 --- a/examples/gno.land/p/moul/typeutil/typeutil.gno +++ b/examples/gno.land/p/moul/typeutil/typeutil.gno @@ -23,7 +23,7 @@ type stringer interface { // - Numbers: int, int8-64, uint, uint8-64, float32, float64 // - Special: time.Time, std.Address, []byte // - Slices: []T for most basic types -// - Maps: map[string]string, map[string]interface{} +// - Maps: map[string]string, map[string]any // - Interface: types implementing String() string // // Example usage: @@ -34,7 +34,7 @@ type stringer interface { // "a": "1", // "b": "2", // }) -func ToString(val interface{}) string { +func ToString(val any) string { if val == nil { return "" } @@ -136,7 +136,7 @@ func ToString(val interface{}) string { return join(v) case []float64: return join(v) - case []interface{}: + case []any: return join(v) case []time.Time: return joinTimes(v) @@ -148,16 +148,16 @@ func ToString(val interface{}) string { return joinBytes(v) // Map types with various key types - case map[interface{}]interface{}, map[string]interface{}, map[string]string, map[string]int: + case map[any]any, map[string]any, map[string]string, map[string]int: var b strings.Builder b.WriteString("map[") first := true switch m := v.(type) { - case map[interface{}]interface{}: + case map[any]any: // Convert all keys to strings for consistent ordering keys := make([]string, 0) - keyMap := make(map[string]interface{}) + keyMap := make(map[string]any) for k := range m { keyStr := ToString(k) @@ -177,7 +177,7 @@ func ToString(val interface{}) string { first = false } - case map[string]interface{}: + case map[string]any: keys := make([]string, 0) for k := range m { keys = append(keys, k) @@ -237,7 +237,7 @@ func ToString(val interface{}) string { } } -func join(slice interface{}) string { +func join(slice any) string { if IsZero(slice) { return "[]" } @@ -314,7 +314,7 @@ func joinBytes(slice [][]byte) string { // - Slices/Maps: empty is false, non-empty is true // - nil: always false // - bool: direct value -func ToBool(val interface{}) bool { +func ToBool(val any) bool { if IsZero(val) { return false } @@ -337,7 +337,7 @@ func ToBool(val interface{}) bool { // - bool: false // - time.Time: IsZero() // - std.Address: empty string -func IsZero(val interface{}) bool { +func IsZero(val any) bool { if val == nil { return true } @@ -414,7 +414,7 @@ func IsZero(val interface{}) bool { return len(v) == 0 case []float64: return len(v) == 0 - case []interface{}: + case []any: return len(v) == 0 case []time.Time: return len(v) == 0 @@ -428,7 +428,7 @@ func IsZero(val interface{}) bool { // Maps (check if empty) case map[string]string: return len(v) == 0 - case map[string]interface{}: + case map[string]any: return len(v) == 0 default: @@ -436,49 +436,49 @@ func IsZero(val interface{}) bool { } } -// ToInterfaceSlice converts various slice types to []interface{} -func ToInterfaceSlice(val interface{}) []interface{} { +// ToInterfaceSlice converts various slice types to []any +func ToInterfaceSlice(val any) []any { switch v := val.(type) { - case []interface{}: + case []any: return v case []string: - result := make([]interface{}, len(v)) + result := make([]any, len(v)) for i, s := range v { result[i] = s } return result case []int: - result := make([]interface{}, len(v)) + result := make([]any, len(v)) for i, n := range v { result[i] = n } return result case []int32: - result := make([]interface{}, len(v)) + result := make([]any, len(v)) for i, n := range v { result[i] = n } return result case []int64: - result := make([]interface{}, len(v)) + result := make([]any, len(v)) for i, n := range v { result[i] = n } return result case []float32: - result := make([]interface{}, len(v)) + result := make([]any, len(v)) for i, n := range v { result[i] = n } return result case []float64: - result := make([]interface{}, len(v)) + result := make([]any, len(v)) for i, n := range v { result[i] = n } return result case []bool: - result := make([]interface{}, len(v)) + result := make([]any, len(v)) for i, b := range v { result[i] = b } @@ -488,12 +488,12 @@ func ToInterfaceSlice(val interface{}) []interface{} { } } -// ToMapStringInterface converts a map with string keys and any value type to map[string]interface{} -func ToMapStringInterface(m interface{}) (map[string]interface{}, error) { - result := make(map[string]interface{}) +// ToMapStringInterface converts a map with string keys and any value type to map[string]any +func ToMapStringInterface(m any) (map[string]any, error) { + result := make(map[string]any) switch v := m.(type) { - case map[string]interface{}: + case map[string]any: return v, nil case map[string]string: for k, val := range v { @@ -523,11 +523,11 @@ func ToMapStringInterface(m interface{}) (map[string]interface{}, error) { for k, val := range v { result[k] = ToInterfaceSlice(val) } - case map[string][]interface{}: + case map[string][]any: for k, val := range v { result[k] = val } - case map[string]map[string]interface{}: + case map[string]map[string]any: for k, val := range v { result[k] = val } @@ -546,12 +546,12 @@ func ToMapStringInterface(m interface{}) (map[string]interface{}, error) { return result, nil } -// ToMapIntInterface converts a map with int keys and any value type to map[int]interface{} -func ToMapIntInterface(m interface{}) (map[int]interface{}, error) { - result := make(map[int]interface{}) +// ToMapIntInterface converts a map with int keys and any value type to map[int]any +func ToMapIntInterface(m any) (map[int]any, error) { + result := make(map[int]any) switch v := m.(type) { - case map[int]interface{}: + case map[int]any: return v, nil case map[int]string: for k, val := range v { @@ -581,15 +581,15 @@ func ToMapIntInterface(m interface{}) (map[int]interface{}, error) { for k, val := range v { result[k] = ToInterfaceSlice(val) } - case map[int][]interface{}: + case map[int][]any: for k, val := range v { result[k] = val } - case map[int]map[string]interface{}: + case map[int]map[string]any: for k, val := range v { result[k] = val } - case map[int]map[int]interface{}: + case map[int]map[int]any: for k, val := range v { result[k] = val } @@ -601,11 +601,11 @@ func ToMapIntInterface(m interface{}) (map[int]interface{}, error) { } // ToStringSlice converts various slice types to []string -func ToStringSlice(val interface{}) []string { +func ToStringSlice(val any) []string { switch v := val.(type) { case []string: return v - case []interface{}: + case []any: result := make([]string, len(v)) for i, item := range v { result[i] = ToString(item) diff --git a/examples/gno.land/p/moul/typeutil/typeutil_test.gno b/examples/gno.land/p/moul/typeutil/typeutil_test.gno index 543ea1deec4..4dcccd68c8f 100644 --- a/examples/gno.land/p/moul/typeutil/typeutil_test.gno +++ b/examples/gno.land/p/moul/typeutil/typeutil_test.gno @@ -26,7 +26,7 @@ func TestToString(t *testing.T) { type testCase struct { name string - input interface{} + input any expected string } @@ -73,7 +73,7 @@ func TestToString(t *testing.T) { {"bytes_slice", [][]byte{[]byte("a"), []byte("b")}, "[a b]"}, {"time_slice", []time.Time{now, now}, "[" + now.String() + " " + now.String() + "]"}, {"address_slice", []std.Address{addr, addr}, "[" + string(addr) + " " + string(addr) + "]"}, - {"interface_slice", []interface{}{1, "a", true}, "[1 a true]"}, + {"interface_slice", []any{1, "a", true}, "[1 a true]"}, // empty slices {"empty_string_slice", []string{}, "[]"}, @@ -85,17 +85,17 @@ func TestToString(t *testing.T) { {"empty_bytes_slice", [][]byte{}, "[]"}, {"empty_time_slice", []time.Time{}, "[]"}, {"empty_address_slice", []std.Address{}, "[]"}, - {"empty_interface_slice", []interface{}{}, "[]"}, + {"empty_interface_slice", []any{}, "[]"}, // maps {"empty_string_map", map[string]string{}, "map[]"}, {"string_map", map[string]string{"a": "1", "b": "2"}, "map[a:1 b:2]"}, - {"empty_interface_map", map[string]interface{}{}, "map[]"}, - {"interface_map", map[string]interface{}{"a": 1, "b": "2"}, "map[a:1 b:2]"}, + {"empty_interface_map", map[string]any{}, "map[]"}, + {"interface_map", map[string]any{"a": 1, "b": "2"}, "map[a:1 b:2]"}, // edge cases {"empty_bytes", []byte{}, ""}, - {"nil_interface", interface{}(nil), ""}, + {"nil_interface", any(nil), ""}, {"empty_struct", struct{}{}, "{}"}, {"unknown_type", struct{ foo string }{}, ""}, @@ -134,7 +134,7 @@ func TestToBool(t *testing.T) { type testCase struct { name string - input interface{} + input any expected bool } @@ -217,7 +217,7 @@ func TestIsZero(t *testing.T) { type testCase struct { name string - input interface{} + input any expected bool } @@ -278,15 +278,11 @@ func TestIsZero(t *testing.T) { } func TestToInterfaceSlice(t *testing.T) { - now := time.Now() - addr := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - str := testStringer{value: "hello"} - tests := []struct { name string - input interface{} - expected []interface{} - compare func([]interface{}, []interface{}) bool + input any + expected []any + compare func([]any, []any) bool }{ { name: "nil", @@ -296,74 +292,74 @@ func TestToInterfaceSlice(t *testing.T) { }, { name: "empty_interface_slice", - input: []interface{}{}, - expected: []interface{}{}, + input: []any{}, + expected: []any{}, compare: compareEmpty, }, { name: "interface_slice", - input: []interface{}{1, "two", true}, - expected: []interface{}{1, "two", true}, + input: []any{1, "two", true}, + expected: []any{1, "two", true}, compare: compareInterfaces, }, { name: "string_slice", input: []string{"a", "b", "c"}, - expected: []interface{}{"a", "b", "c"}, + expected: []any{"a", "b", "c"}, compare: compareStrings, }, { name: "int_slice", input: []int{1, 2, 3}, - expected: []interface{}{1, 2, 3}, + expected: []any{1, 2, 3}, compare: compareInts, }, { name: "int32_slice", input: []int32{1, 2, 3}, - expected: []interface{}{int32(1), int32(2), int32(3)}, + expected: []any{int32(1), int32(2), int32(3)}, compare: compareInt32s, }, { name: "int64_slice", input: []int64{1, 2, 3}, - expected: []interface{}{int64(1), int64(2), int64(3)}, + expected: []any{int64(1), int64(2), int64(3)}, compare: compareInt64s, }, { name: "float32_slice", input: []float32{1.1, 2.2, 3.3}, - expected: []interface{}{float32(1.1), float32(2.2), float32(3.3)}, + expected: []any{float32(1.1), float32(2.2), float32(3.3)}, compare: compareFloat32s, }, { name: "float64_slice", input: []float64{1.1, 2.2, 3.3}, - expected: []interface{}{1.1, 2.2, 3.3}, + expected: []any{1.1, 2.2, 3.3}, compare: compareFloat64s, }, { name: "bool_slice", input: []bool{true, false, true}, - expected: []interface{}{true, false, true}, + expected: []any{true, false, true}, compare: compareBools, }, /* { name: "time_slice", input: []time.Time{now}, - expected: []interface{}{now}, + expected: []any{now}, compare: compareTimes, }, */ // TODO: fix this /* { name: "address_slice", input: []std.Address{addr}, - expected: []interface{}{addr}, + expected: []any{addr}, compare: compareAddresses, },*/ // TODO: fix this /* { name: "bytes_slice", input: [][]byte{[]byte("hello"), []byte("world")}, - expected: []interface{}{[]byte("hello"), []byte("world")}, + expected: []any{[]byte("hello"), []byte("world")}, compare: compareBytes, },*/ // TODO: fix this } @@ -378,15 +374,15 @@ func TestToInterfaceSlice(t *testing.T) { } } -func compareNil(a, b []interface{}) bool { +func compareNil(a, b []any) bool { return a == nil && b == nil } -func compareEmpty(a, b []interface{}) bool { +func compareEmpty(a, b []any) bool { return len(a) == 0 && len(b) == 0 } -func compareInterfaces(a, b []interface{}) bool { +func compareInterfaces(a, b []any) bool { if len(a) != len(b) { return false } @@ -398,7 +394,7 @@ func compareInterfaces(a, b []interface{}) bool { return true } -func compareStrings(a, b []interface{}) bool { +func compareStrings(a, b []any) bool { if len(a) != len(b) { return false } @@ -412,7 +408,7 @@ func compareStrings(a, b []interface{}) bool { return true } -func compareInts(a, b []interface{}) bool { +func compareInts(a, b []any) bool { if len(a) != len(b) { return false } @@ -426,7 +422,7 @@ func compareInts(a, b []interface{}) bool { return true } -func compareInt32s(a, b []interface{}) bool { +func compareInt32s(a, b []any) bool { if len(a) != len(b) { return false } @@ -440,7 +436,7 @@ func compareInt32s(a, b []interface{}) bool { return true } -func compareInt64s(a, b []interface{}) bool { +func compareInt64s(a, b []any) bool { if len(a) != len(b) { return false } @@ -454,7 +450,7 @@ func compareInt64s(a, b []interface{}) bool { return true } -func compareFloat32s(a, b []interface{}) bool { +func compareFloat32s(a, b []any) bool { if len(a) != len(b) { return false } @@ -468,7 +464,7 @@ func compareFloat32s(a, b []interface{}) bool { return true } -func compareFloat64s(a, b []interface{}) bool { +func compareFloat64s(a, b []any) bool { if len(a) != len(b) { return false } @@ -482,7 +478,7 @@ func compareFloat64s(a, b []interface{}) bool { return true } -func compareBools(a, b []interface{}) bool { +func compareBools(a, b []any) bool { if len(a) != len(b) { return false } @@ -496,7 +492,7 @@ func compareBools(a, b []interface{}) bool { return true } -func compareTimes(a, b []interface{}) bool { +func compareTimes(a, b []any) bool { if len(a) != len(b) { return false } @@ -510,7 +506,7 @@ func compareTimes(a, b []interface{}) bool { return true } -func compareAddresses(a, b []interface{}) bool { +func compareAddresses(a, b []any) bool { if len(a) != len(b) { return false } @@ -524,7 +520,7 @@ func compareAddresses(a, b []interface{}) bool { return true } -func compareBytes(a, b []interface{}) bool { +func compareBytes(a, b []any) bool { if len(a) != len(b) { return false } @@ -538,8 +534,8 @@ func compareBytes(a, b []interface{}) bool { return true } -// compareStringInterfaceMaps compares two map[string]interface{} for equality -func compareStringInterfaceMaps(a, b map[string]interface{}) bool { +// compareStringInterfaceMaps compares two map[string]any for equality +func compareStringInterfaceMaps(a, b map[string]any) bool { if len(a) != len(b) { return false } @@ -570,8 +566,8 @@ func compareStringInterfaceMaps(a, b map[string]interface{}) bool { if !ok || val1 != val2 { return false } - case []interface{}: - val2, ok := v2.([]interface{}) + case []any: + val2, ok := v2.([]any) if !ok || len(val1) != len(val2) { return false } @@ -580,8 +576,8 @@ func compareStringInterfaceMaps(a, b map[string]interface{}) bool { return false } } - case map[string]interface{}: - val2, ok := v2.(map[string]interface{}) + case map[string]any: + val2, ok := v2.(map[string]any) if !ok || !compareStringInterfaceMaps(val1, val2) { return false } @@ -595,17 +591,17 @@ func compareStringInterfaceMaps(a, b map[string]interface{}) bool { func TestToMapStringInterface(t *testing.T) { tests := []struct { name string - input interface{} - expected map[string]interface{} + input any + expected map[string]any wantErr bool }{ { - name: "map[string]interface{}", - input: map[string]interface{}{ + name: "map[string]any", + input: map[string]any{ "key1": "value1", "key2": 42, }, - expected: map[string]interface{}{ + expected: map[string]any{ "key1": "value1", "key2": 42, }, @@ -617,7 +613,7 @@ func TestToMapStringInterface(t *testing.T) { "key1": "value1", "key2": "value2", }, - expected: map[string]interface{}{ + expected: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -629,7 +625,7 @@ func TestToMapStringInterface(t *testing.T) { "key1": 1, "key2": 2, }, - expected: map[string]interface{}{ + expected: map[string]any{ "key1": 1, "key2": 2, }, @@ -641,7 +637,7 @@ func TestToMapStringInterface(t *testing.T) { "key1": 1.1, "key2": 2.2, }, - expected: map[string]interface{}{ + expected: map[string]any{ "key1": 1.1, "key2": 2.2, }, @@ -653,7 +649,7 @@ func TestToMapStringInterface(t *testing.T) { "key1": true, "key2": false, }, - expected: map[string]interface{}{ + expected: map[string]any{ "key1": true, "key2": false, }, @@ -665,9 +661,9 @@ func TestToMapStringInterface(t *testing.T) { "key1": {"a", "b"}, "key2": {"c", "d"}, }, - expected: map[string]interface{}{ - "key1": []interface{}{"a", "b"}, - "key2": []interface{}{"c", "d"}, + expected: map[string]any{ + "key1": []any{"a", "b"}, + "key2": []any{"c", "d"}, }, wantErr: false, }, @@ -677,9 +673,9 @@ func TestToMapStringInterface(t *testing.T) { "key1": {"nested1": "value1"}, "key2": {"nested2": "value2"}, }, - expected: map[string]interface{}{ - "key1": map[string]interface{}{"nested1": "value1"}, - "key2": map[string]interface{}{"nested2": "value2"}, + expected: map[string]any{ + "key1": map[string]any{"nested1": "value1"}, + "key2": map[string]any{"nested2": "value2"}, }, wantErr: false, }, @@ -715,8 +711,8 @@ func TestToMapStringInterfaceErrors(t *testing.T) { } } -// compareIntInterfaceMaps compares two map[int]interface{} for equality -func compareIntInterfaceMaps(a, b map[int]interface{}) bool { +// compareIntInterfaceMaps compares two map[int]any for equality +func compareIntInterfaceMaps(a, b map[int]any) bool { if len(a) != len(b) { return false } @@ -747,8 +743,8 @@ func compareIntInterfaceMaps(a, b map[int]interface{}) bool { if !ok || val1 != val2 { return false } - case []interface{}: - val2, ok := v2.([]interface{}) + case []any: + val2, ok := v2.([]any) if !ok || len(val1) != len(val2) { return false } @@ -757,8 +753,8 @@ func compareIntInterfaceMaps(a, b map[int]interface{}) bool { return false } } - case map[string]interface{}: - val2, ok := v2.(map[string]interface{}) + case map[string]any: + val2, ok := v2.(map[string]any) if !ok || !compareStringInterfaceMaps(val1, val2) { return false } @@ -772,17 +768,17 @@ func compareIntInterfaceMaps(a, b map[int]interface{}) bool { func TestToMapIntInterface(t *testing.T) { tests := []struct { name string - input interface{} - expected map[int]interface{} + input any + expected map[int]any wantErr bool }{ { - name: "map[int]interface{}", - input: map[int]interface{}{ + name: "map[int]any", + input: map[int]any{ 1: "value1", 2: 42, }, - expected: map[int]interface{}{ + expected: map[int]any{ 1: "value1", 2: 42, }, @@ -794,7 +790,7 @@ func TestToMapIntInterface(t *testing.T) { 1: "value1", 2: "value2", }, - expected: map[int]interface{}{ + expected: map[int]any{ 1: "value1", 2: "value2", }, @@ -806,7 +802,7 @@ func TestToMapIntInterface(t *testing.T) { 1: 10, 2: 20, }, - expected: map[int]interface{}{ + expected: map[int]any{ 1: 10, 2: 20, }, @@ -818,7 +814,7 @@ func TestToMapIntInterface(t *testing.T) { 1: 1.1, 2: 2.2, }, - expected: map[int]interface{}{ + expected: map[int]any{ 1: 1.1, 2: 2.2, }, @@ -830,7 +826,7 @@ func TestToMapIntInterface(t *testing.T) { 1: true, 2: false, }, - expected: map[int]interface{}{ + expected: map[int]any{ 1: true, 2: false, }, @@ -842,21 +838,21 @@ func TestToMapIntInterface(t *testing.T) { 1: {"a", "b"}, 2: {"c", "d"}, }, - expected: map[int]interface{}{ - 1: []interface{}{"a", "b"}, - 2: []interface{}{"c", "d"}, + expected: map[int]any{ + 1: []any{"a", "b"}, + 2: []any{"c", "d"}, }, wantErr: false, }, { - name: "map[int]map[string]interface{}", - input: map[int]map[string]interface{}{ + name: "map[int]map[string]any", + input: map[int]map[string]any{ 1: {"nested1": "value1"}, 2: {"nested2": "value2"}, }, - expected: map[int]interface{}{ - 1: map[string]interface{}{"nested1": "value1"}, - 2: map[string]interface{}{"nested2": "value2"}, + expected: map[int]any{ + 1: map[string]any{"nested1": "value1"}, + 2: map[string]any{"nested2": "value2"}, }, wantErr: false, }, @@ -887,7 +883,7 @@ func TestToMapIntInterface(t *testing.T) { func TestToStringSlice(t *testing.T) { tests := []struct { name string - input interface{} + input any expected []string }{ { @@ -967,12 +963,12 @@ func TestToStringSlice(t *testing.T) { }, { name: "interface slice", - input: []interface{}{1, "hello", true}, + input: []any{1, "hello", true}, expected: []string{"1", "hello", "true"}, }, { name: "time slice", - input: []time.Time{time.Time{}, time.Time{}}, + input: []time.Time{{}, {}}, expected: []string{"0001-01-01 00:00:00 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC"}, }, { @@ -1013,12 +1009,12 @@ func slicesEqual(a, b []string) bool { func TestToStringAdvanced(t *testing.T) { tests := []struct { name string - input interface{} + input any expected string }{ { name: "slice with mixed basic types", - input: []interface{}{ + input: []any{ 42, "hello", true, @@ -1028,7 +1024,7 @@ func TestToStringAdvanced(t *testing.T) { }, { name: "map with basic types", - input: map[string]interface{}{ + input: map[string]any{ "int": 42, "str": "hello", "bool": true, @@ -1038,7 +1034,7 @@ func TestToStringAdvanced(t *testing.T) { }, { name: "mixed types map", - input: map[interface{}]interface{}{ + input: map[any]any{ 42: "number", "string": 123, true: []int{1, 2, 3}, @@ -1048,12 +1044,12 @@ func TestToStringAdvanced(t *testing.T) { }, { name: "nested maps", - input: map[string]interface{}{ + input: map[string]any{ "a": map[string]int{ "x": 1, "y": 2, }, - "b": []interface{}{1, "two", true}, + "b": []any{1, "two", true}, }, expected: "map[a:map[x:1 y:2] b:[1 two true]]", }, diff --git a/examples/gno.land/p/moul/ulist/ulist.gno b/examples/gno.land/p/moul/ulist/ulist.gno index 507a02a4e45..48e98b79b53 100644 --- a/examples/gno.land/p/moul/ulist/ulist.gno +++ b/examples/gno.land/p/moul/ulist/ulist.gno @@ -45,12 +45,12 @@ type List struct { // and Value is the stored data type Entry struct { Index int - Value interface{} + Value any } // treeNode represents a node in the binary tree type treeNode struct { - data interface{} + data any left *treeNode right *treeNode } @@ -68,7 +68,7 @@ func New() *List { // Append adds one or more values to the end of the list. // Values are added sequentially, and the list grows automatically. -func (l *List) Append(values ...interface{}) { +func (l *List) Append(values ...any) { for _, value := range values { index := l.totalSize node := l.findNode(index, true) @@ -80,7 +80,7 @@ func (l *List) Append(values ...interface{}) { // Get retrieves the value at the specified index. // Returns nil if the index is out of bounds or if the element was deleted. -func (l *List) Get(index int) interface{} { +func (l *List) Get(index int) any { node := l.findNode(index, false) if node == nil { return nil @@ -117,7 +117,7 @@ func (l *List) Delete(indices ...int) error { // Set updates or restores a value at the specified index if within bounds // Returns ErrOutOfBounds if the index is invalid -func (l *List) Set(index int, value interface{}) error { +func (l *List) Set(index int, value any) error { if l == nil || index < 0 || index >= l.totalSize { return ErrOutOfBounds } @@ -160,7 +160,7 @@ func (l *List) TotalSize() int { // IterCbFn is a callback function type used in iteration methods. // Return true to stop iteration, false to continue. -type IterCbFn func(index int, value interface{}) bool +type IterCbFn func(index int, value any) bool // Iterator performs iteration between start and end indices, calling cb for each entry. // If start > end, iteration is performed in reverse order. @@ -246,7 +246,7 @@ func (l *List) IteratorByOffset(offset int, count int, cb IterCbFn) bool { // Wrap the callback to limit iterations remaining := abs(count) - wrapper := func(index int, value interface{}) bool { + wrapper := func(index int, value any) bool { if remaining <= 0 { wrapperReturned = true return true @@ -365,7 +365,7 @@ func (l *List) MustDelete(indices ...int) { // MustGet retrieves the value at the specified index. // Panics if the index is out of bounds or if the element was deleted. -func (l *List) MustGet(index int) interface{} { +func (l *List) MustGet(index int) any { if l == nil || index < 0 || index >= l.totalSize { panic(ErrOutOfBounds) } @@ -378,7 +378,7 @@ func (l *List) MustGet(index int) interface{} { // MustSet updates or restores a value at the specified index. // Panics if the index is out of bounds. -func (l *List) MustSet(index int, value interface{}) { +func (l *List) MustSet(index int, value any) { if err := l.Set(index, value); err != nil { panic(err) } @@ -389,7 +389,7 @@ func (l *List) MustSet(index int, value interface{}) { // Deleted elements are skipped. func (l *List) GetRange(start, end int) []Entry { var entries []Entry - l.Iterator(start, end, func(index int, value interface{}) bool { + l.Iterator(start, end, func(index int, value any) bool { entries = append(entries, Entry{Index: index, Value: value}) return false }) @@ -402,7 +402,7 @@ func (l *List) GetRange(start, end int) []Entry { // Deleted elements are skipped. func (l *List) GetByOffset(offset int, count int) []Entry { var entries []Entry - l.IteratorByOffset(offset, count, func(index int, value interface{}) bool { + l.IteratorByOffset(offset, count, func(index int, value any) bool { entries = append(entries, Entry{Index: index, Value: value}) return false }) @@ -412,17 +412,17 @@ func (l *List) GetByOffset(offset int, count int) []Entry { // IList defines the interface for an ulist.List compatible structure. type IList interface { // Basic operations - Append(values ...interface{}) - Get(index int) interface{} + Append(values ...any) + Get(index int) any Delete(indices ...int) error Size() int TotalSize() int - Set(index int, value interface{}) error + Set(index int, value any) error // Must variants that panic instead of returning errors MustDelete(indices ...int) - MustGet(index int) interface{} - MustSet(index int, value interface{}) + MustGet(index int) any + MustSet(index int, value any) // Range operations GetRange(start, end int) []Entry diff --git a/examples/gno.land/p/moul/ulist/ulist_test.gno b/examples/gno.land/p/moul/ulist/ulist_test.gno index f098731a7db..ef946565068 100644 --- a/examples/gno.land/p/moul/ulist/ulist_test.gno +++ b/examples/gno.land/p/moul/ulist/ulist_test.gno @@ -19,7 +19,7 @@ func TestListAppendAndGet(t *testing.T) { name string setup func() *List index int - expected interface{} + expected any }{ { name: "empty list", @@ -159,8 +159,8 @@ func TestListAppendAndGet(t *testing.T) { } // generateSequence creates a slice of integers from 0 to n-1 -func generateSequence(n int) []interface{} { - result := make([]interface{}, n) +func generateSequence(n int) []any { + result := make([]any, n) for i := 0; i < n; i++ { result[i] = i } @@ -291,7 +291,7 @@ func TestListSizeAndTotalSize(t *testing.T) { func TestIterator(t *testing.T) { tests := []struct { name string - values []interface{} + values []any start int end int expected []Entry @@ -300,7 +300,7 @@ func TestIterator(t *testing.T) { }{ { name: "empty list", - values: []interface{}{}, + values: []any{}, start: 0, end: 10, expected: []Entry{}, @@ -316,7 +316,7 @@ func TestIterator(t *testing.T) { }, { name: "single element forward", - values: []interface{}{42}, + values: []any{42}, start: 0, end: 0, expected: []Entry{ @@ -326,7 +326,7 @@ func TestIterator(t *testing.T) { }, { name: "multiple elements forward", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, start: 0, end: 4, expected: []Entry{ @@ -340,7 +340,7 @@ func TestIterator(t *testing.T) { }, { name: "multiple elements reverse", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, start: 4, end: 0, expected: []Entry{ @@ -354,7 +354,7 @@ func TestIterator(t *testing.T) { }, { name: "partial range forward", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, start: 1, end: 3, expected: []Entry{ @@ -366,7 +366,7 @@ func TestIterator(t *testing.T) { }, { name: "partial range reverse", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, start: 3, end: 1, expected: []Entry{ @@ -378,7 +378,7 @@ func TestIterator(t *testing.T) { }, { name: "stop iteration early", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, start: 0, end: 4, wantStop: true, @@ -390,7 +390,7 @@ func TestIterator(t *testing.T) { }, { name: "negative start", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, start: -1, end: 2, expected: []Entry{ @@ -402,7 +402,7 @@ func TestIterator(t *testing.T) { }, { name: "negative end", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, start: 0, end: -2, expected: []Entry{ @@ -412,7 +412,7 @@ func TestIterator(t *testing.T) { }, { name: "start beyond size", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, start: 5, end: 6, expected: []Entry{}, @@ -420,7 +420,7 @@ func TestIterator(t *testing.T) { }, { name: "end beyond size", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, start: 0, end: 5, expected: []Entry{ @@ -432,7 +432,7 @@ func TestIterator(t *testing.T) { }, { name: "with deleted elements", - values: []interface{}{1, 2, nil, 4, 5}, + values: []any{1, 2, nil, 4, 5}, start: 0, end: 4, expected: []Entry{ @@ -445,7 +445,7 @@ func TestIterator(t *testing.T) { }, { name: "with deleted elements reverse", - values: []interface{}{1, nil, 3, nil, 5}, + values: []any{1, nil, 3, nil, 5}, start: 4, end: 0, expected: []Entry{ @@ -457,7 +457,7 @@ func TestIterator(t *testing.T) { }, { name: "start equals end", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, start: 1, end: 1, expected: []Entry{{Index: 1, Value: 2}}, @@ -471,7 +471,7 @@ func TestIterator(t *testing.T) { list.Append(tt.values...) var result []Entry - stopped := list.Iterator(tt.start, tt.end, func(index int, value interface{}) bool { + stopped := list.Iterator(tt.start, tt.end, func(index int, value any) bool { result = append(result, Entry{Index: index, Value: value}) return tt.stopAfter >= 0 && len(result) >= tt.stopAfter }) @@ -580,7 +580,7 @@ func TestEdgeCases(t *testing.T) { l.Append(1, 2, 3) l.Delete(0, 1, 2) var count int - l.Iterator(0, 2, func(index int, value interface{}) bool { + l.Iterator(0, 2, func(index int, value any) bool { count++ return false }) @@ -613,7 +613,7 @@ func TestEdgeCases(t *testing.T) { func TestIteratorByOffset(t *testing.T) { tests := []struct { name string - values []interface{} + values []any offset int count int expected []Entry @@ -621,7 +621,7 @@ func TestIteratorByOffset(t *testing.T) { }{ { name: "empty list", - values: []interface{}{}, + values: []any{}, offset: 0, count: 5, expected: []Entry{}, @@ -629,7 +629,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "positive count forward iteration", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, offset: 1, count: 2, expected: []Entry{ @@ -640,7 +640,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "negative count backward iteration", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, offset: 3, count: -2, expected: []Entry{ @@ -651,7 +651,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "count exceeds available elements forward", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, offset: 1, count: 5, expected: []Entry{ @@ -662,7 +662,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "count exceeds available elements backward", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, offset: 1, count: -5, expected: []Entry{ @@ -673,7 +673,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "zero count", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, offset: 0, count: 0, expected: []Entry{}, @@ -681,7 +681,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "negative offset", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, offset: -1, count: 2, expected: []Entry{ @@ -692,7 +692,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "offset beyond size", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, offset: 5, count: -2, expected: []Entry{ @@ -703,7 +703,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "with deleted elements", - values: []interface{}{1, nil, 3, nil, 5}, + values: []any{1, nil, 3, nil, 5}, offset: 0, count: 3, expected: []Entry{ @@ -715,7 +715,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "early stop in forward iteration", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, offset: 0, count: 5, expected: []Entry{ @@ -726,7 +726,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "early stop in backward iteration", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, offset: 4, count: -5, expected: []Entry{ @@ -745,7 +745,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "single element forward", - values: []interface{}{1}, + values: []any{1}, offset: 0, count: 5, expected: []Entry{ @@ -755,7 +755,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "single element backward", - values: []interface{}{1}, + values: []any{1}, offset: 0, count: -5, expected: []Entry{ @@ -765,7 +765,7 @@ func TestIteratorByOffset(t *testing.T) { }, { name: "all deleted elements", - values: []interface{}{nil, nil, nil}, + values: []any{nil, nil, nil}, offset: 0, count: 3, expected: []Entry{}, @@ -781,12 +781,12 @@ func TestIteratorByOffset(t *testing.T) { var result []Entry var cb IterCbFn if tt.wantStop { - cb = func(index int, value interface{}) bool { + cb = func(index int, value any) bool { result = append(result, Entry{Index: index, Value: value}) return len(result) >= 2 // Stop after 2 elements for early stop tests } } else { - cb = func(index int, value interface{}) bool { + cb = func(index int, value any) bool { result = append(result, Entry{Index: index, Value: value}) return false } @@ -876,7 +876,7 @@ func TestMustGet(t *testing.T) { name string setup func() *List index int - expected interface{} + expected any shouldPanic bool panicMsg string }{ @@ -964,21 +964,21 @@ func TestMustGet(t *testing.T) { func TestGetRange(t *testing.T) { tests := []struct { name string - values []interface{} + values []any start int end int expected []Entry }{ { name: "empty list", - values: []interface{}{}, + values: []any{}, start: 0, end: 10, expected: []Entry{}, }, { name: "single element", - values: []interface{}{42}, + values: []any{42}, start: 0, end: 0, expected: []Entry{ @@ -987,7 +987,7 @@ func TestGetRange(t *testing.T) { }, { name: "multiple elements forward", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, start: 1, end: 3, expected: []Entry{ @@ -998,7 +998,7 @@ func TestGetRange(t *testing.T) { }, { name: "multiple elements reverse", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, start: 3, end: 1, expected: []Entry{ @@ -1009,7 +1009,7 @@ func TestGetRange(t *testing.T) { }, { name: "with deleted elements", - values: []interface{}{1, nil, 3, nil, 5}, + values: []any{1, nil, 3, nil, 5}, start: 0, end: 4, expected: []Entry{ @@ -1027,14 +1027,14 @@ func TestGetRange(t *testing.T) { }, { name: "negative indices", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, start: -1, end: -2, expected: []Entry{}, }, { name: "indices beyond size", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, start: 1, end: 5, expected: []Entry{ @@ -1063,21 +1063,21 @@ func TestGetRange(t *testing.T) { func TestGetByOffset(t *testing.T) { tests := []struct { name string - values []interface{} + values []any offset int count int expected []Entry }{ { name: "empty list", - values: []interface{}{}, + values: []any{}, offset: 0, count: 5, expected: []Entry{}, }, { name: "positive count forward", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, offset: 1, count: 2, expected: []Entry{ @@ -1087,7 +1087,7 @@ func TestGetByOffset(t *testing.T) { }, { name: "negative count backward", - values: []interface{}{1, 2, 3, 4, 5}, + values: []any{1, 2, 3, 4, 5}, offset: 3, count: -2, expected: []Entry{ @@ -1097,7 +1097,7 @@ func TestGetByOffset(t *testing.T) { }, { name: "count exceeds available elements", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, offset: 1, count: 5, expected: []Entry{ @@ -1107,14 +1107,14 @@ func TestGetByOffset(t *testing.T) { }, { name: "zero count", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, offset: 0, count: 0, expected: []Entry{}, }, { name: "with deleted elements", - values: []interface{}{1, nil, 3, nil, 5}, + values: []any{1, nil, 3, nil, 5}, offset: 0, count: 3, expected: []Entry{ @@ -1125,7 +1125,7 @@ func TestGetByOffset(t *testing.T) { }, { name: "negative offset", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, offset: -1, count: 2, expected: []Entry{ @@ -1135,7 +1135,7 @@ func TestGetByOffset(t *testing.T) { }, { name: "offset beyond size", - values: []interface{}{1, 2, 3}, + values: []any{1, 2, 3}, offset: 5, count: -2, expected: []Entry{ @@ -1173,7 +1173,7 @@ func TestMustSet(t *testing.T) { name string setup func() *List index int - value interface{} + value any shouldPanic bool panicMsg string }{ @@ -1270,7 +1270,7 @@ func TestSet(t *testing.T) { name string setup func() *List index int - value interface{} + value any expectedErr error verify func(t *testing.T, l *List) }{ diff --git a/examples/gno.land/p/n2p5/loci/loci.gno b/examples/gno.land/p/n2p5/loci/loci.gno index 7bd5c29c3af..a50c537a8f4 100644 --- a/examples/gno.land/p/n2p5/loci/loci.gno +++ b/examples/gno.land/p/n2p5/loci/loci.gno @@ -25,10 +25,10 @@ func New() *LociStore { } } -// Set stores a byte slice in the AVL tree using the `std.PrevRealm().Addr()` +// Set stores a byte slice in the AVL tree using the `std.PreviousRealm().Address()` // string as the key. func (s *LociStore) Set(value []byte) { - key := string(std.PrevRealm().Addr()) + key := string(std.PreviousRealm().Address()) s.internal.Set(key, value) } diff --git a/examples/gno.land/p/n2p5/loci/loci_test.gno b/examples/gno.land/p/n2p5/loci/loci_test.gno index bb216a8539e..ad5342edf2d 100644 --- a/examples/gno.land/p/n2p5/loci/loci_test.gno +++ b/examples/gno.land/p/n2p5/loci/loci_test.gno @@ -8,11 +8,6 @@ import ( ) func TestLociStore(t *testing.T) { - t.Parallel() - - u1 := testutils.TestAddress("u1") - u2 := testutils.TestAddress("u1") - t.Run("TestSet", func(t *testing.T) { t.Parallel() store := New() @@ -20,7 +15,7 @@ func TestLociStore(t *testing.T) { m1 := []byte("hello") m2 := []byte("world") - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) // Ensure that the value is nil before setting it. r1 := store.Get(u1) @@ -52,11 +47,11 @@ func TestLociStore(t *testing.T) { m2 := []byte("world") m3 := []byte("goodbye") - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) store.Set(m1) - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) store.Set(m2) - std.TestSetOrigCaller(u3) + std.TestSetOriginCaller(u3) store.Set(m3) // Ensure that the value is correct after setting it. diff --git a/examples/gno.land/p/n2p5/mgroup/mgroup.gno b/examples/gno.land/p/n2p5/mgroup/mgroup.gno index 566d625a003..2118c4e08cd 100644 --- a/examples/gno.land/p/n2p5/mgroup/mgroup.gno +++ b/examples/gno.land/p/n2p5/mgroup/mgroup.gno @@ -74,7 +74,7 @@ func (g *ManagedGroup) RemoveBackupOwner(addr std.Address) error { // If the caller is not a backup owner, an error is returned. // The caller is automatically added as a member of the group. func (g *ManagedGroup) ClaimOwnership() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() // already owner, skip if caller == g.Owner() { return nil @@ -173,7 +173,7 @@ func (g *ManagedGroup) MembersWithOffset(offset, count int) []string { // sliceWithOffset is a helper function to iterate over an avl.Tree with an offset and count. func sliceWithOffset(t *avl.Tree, offset, count int) []string { var result []string - t.IterateByOffset(offset, count, func(k string, _ interface{}) bool { + t.IterateByOffset(offset, count, func(k string, _ any) bool { if k == "" { return true } diff --git a/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno b/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno index cd02db98683..13db176d8bf 100644 --- a/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno +++ b/examples/gno.land/p/n2p5/mgroup/mgroup_test.gno @@ -21,7 +21,7 @@ func TestManagedGroup(t *testing.T) { g := New(u1) // happy path { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.AddBackupOwner(u2) if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -29,7 +29,7 @@ func TestManagedGroup(t *testing.T) { } // ensure checking for authorized caller { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.AddBackupOwner(u3) if err != ownable.ErrUnauthorized { t.Errorf("expected %v, got %v", ErrNotBackupOwner.Error(), err.Error()) @@ -37,7 +37,7 @@ func TestManagedGroup(t *testing.T) { } // ensure invalid address is caught { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) var badAddr std.Address err := g.AddBackupOwner(badAddr) if err != ErrInvalidAddress { @@ -50,7 +50,7 @@ func TestManagedGroup(t *testing.T) { g := New(u1) // happy path { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) g.AddBackupOwner(u2) err := g.RemoveBackupOwner(u2) if err != nil { @@ -59,7 +59,7 @@ func TestManagedGroup(t *testing.T) { } // running this twice should not error. { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.RemoveBackupOwner(u2) if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -67,14 +67,14 @@ func TestManagedGroup(t *testing.T) { } // ensure checking for authorized caller { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.RemoveBackupOwner(u3) if err != ownable.ErrUnauthorized { t.Errorf("expected %v, got %v", ErrNotBackupOwner.Error(), err.Error()) } } { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) var badAddr std.Address err := g.RemoveBackupOwner(badAddr) if err != ErrInvalidAddress { @@ -82,7 +82,7 @@ func TestManagedGroup(t *testing.T) { } } { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.RemoveBackupOwner(u1) if err != ErrCannotRemoveOwner { t.Errorf("expected %v, got %v", ErrCannotRemoveOwner.Error(), err.Error()) @@ -95,7 +95,7 @@ func TestManagedGroup(t *testing.T) { g.AddBackupOwner(u2) // happy path { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.ClaimOwnership() if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -109,7 +109,7 @@ func TestManagedGroup(t *testing.T) { } // running this twice should not error. { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.ClaimOwnership() if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -117,7 +117,7 @@ func TestManagedGroup(t *testing.T) { } // ensure checking for authorized caller { - std.TestSetOrigCaller(u3) + std.TestSetOriginCaller(u3) err := g.ClaimOwnership() if err != ErrNotMember { t.Errorf("expected %v, got %v", ErrNotMember.Error(), err.Error()) @@ -129,7 +129,7 @@ func TestManagedGroup(t *testing.T) { g := New(u1) // happy path { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.AddMember(u2) if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -140,7 +140,7 @@ func TestManagedGroup(t *testing.T) { } // ensure checking for authorized caller { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.AddMember(u3) if err != ownable.ErrUnauthorized { t.Errorf("expected %v, got %v", ownable.ErrUnauthorized.Error(), err.Error()) @@ -148,7 +148,7 @@ func TestManagedGroup(t *testing.T) { } // ensure invalid address is caught { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) var badAddr std.Address err := g.AddMember(badAddr) if err != ErrInvalidAddress { @@ -161,7 +161,7 @@ func TestManagedGroup(t *testing.T) { g := New(u1) // happy path { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) g.AddMember(u2) err := g.RemoveMember(u2) if err != nil { @@ -173,7 +173,7 @@ func TestManagedGroup(t *testing.T) { } // running this twice should not error. { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.RemoveMember(u2) if err != nil { t.Errorf("expected nil, got %v", err.Error()) @@ -181,7 +181,7 @@ func TestManagedGroup(t *testing.T) { } // ensure checking for authorized caller { - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) err := g.RemoveMember(u3) if err != ownable.ErrUnauthorized { t.Errorf("expected %v, got %v", ownable.ErrUnauthorized.Error(), err.Error()) @@ -189,7 +189,7 @@ func TestManagedGroup(t *testing.T) { } // ensure invalid address is caught { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) var badAddr std.Address err := g.RemoveMember(badAddr) if err != ErrInvalidAddress { @@ -198,7 +198,7 @@ func TestManagedGroup(t *testing.T) { } // ensure owner cannot be removed { - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) err := g.RemoveMember(u1) if err != ErrCannotRemoveOwner { t.Errorf("expected %v, got %v", ErrCannotRemoveOwner.Error(), err.Error()) @@ -281,7 +281,7 @@ func TestManagedGroup(t *testing.T) { if g.Owner() != u1 { t.Errorf("expected %v, got %v", u1, g.Owner()) } - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) g.ClaimOwnership() if g.Owner() != u2 { t.Errorf("expected %v, got %v", u2, g.Owner()) @@ -289,7 +289,7 @@ func TestManagedGroup(t *testing.T) { }) t.Run("BackupOwners", func(t *testing.T) { t.Parallel() - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) g := New(u1) g.AddBackupOwner(u2) g.AddBackupOwner(u3) @@ -309,7 +309,7 @@ func TestManagedGroup(t *testing.T) { }) t.Run("Members", func(t *testing.T) { t.Parallel() - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) g := New(u1) g.AddMember(u2) g.AddMember(u3) diff --git a/examples/gno.land/p/nt/poa/poa.gno b/examples/gno.land/p/nt/poa/poa.gno index 1eab427f642..043712efffb 100644 --- a/examples/gno.land/p/nt/poa/poa.gno +++ b/examples/gno.land/p/nt/poa/poa.gno @@ -95,7 +95,7 @@ func (p *PoA) GetValidator(address std.Address) (validators.Validator, error) { func (p *PoA) GetValidators() []validators.Validator { vals := make([]validators.Validator, 0, p.validators.Size()) - p.validators.Iterate("", "", func(_ string, value interface{}) bool { + p.validators.Iterate("", "", func(_ string, value any) bool { validator := value.(validators.Validator) vals = append(vals, validator) diff --git a/examples/gno.land/p/oxtekgrinder/ownable2step/errors.gno b/examples/gno.land/p/oxtekgrinder/ownable2step/errors.gno new file mode 100644 index 00000000000..6d91c9eb24b --- /dev/null +++ b/examples/gno.land/p/oxtekgrinder/ownable2step/errors.gno @@ -0,0 +1,10 @@ +package ownable2step + +import "errors" + +var ( + ErrNoPendingOwner = errors.New("ownable2step: no pending owner") + ErrUnauthorized = errors.New("ownable2step: caller is not owner") + ErrPendingUnauthorized = errors.New("ownable2step: caller is not pending owner") + ErrInvalidAddress = errors.New("ownable2step: new owner address is invalid") +) diff --git a/examples/gno.land/p/oxtekgrinder/ownable2step/gno.mod b/examples/gno.land/p/oxtekgrinder/ownable2step/gno.mod new file mode 100644 index 00000000000..0132a03418c --- /dev/null +++ b/examples/gno.land/p/oxtekgrinder/ownable2step/gno.mod @@ -0,0 +1 @@ +module gno.land/p/oxtekgrinder/ownable2step diff --git a/examples/gno.land/p/oxtekgrinder/ownable2step/ownable.gno b/examples/gno.land/p/oxtekgrinder/ownable2step/ownable.gno new file mode 100644 index 00000000000..d81759819b5 --- /dev/null +++ b/examples/gno.land/p/oxtekgrinder/ownable2step/ownable.gno @@ -0,0 +1,98 @@ +package ownable2step + +import ( + "std" +) + +const OwnershipTransferEvent = "OwnershipTransfer" + +// Ownable2Step is a two-step ownership transfer package +// It allows the current owner to set a new owner and the new owner will need to accept the ownership before it is transferred +type Ownable2Step struct { + owner std.Address + pendingOwner std.Address +} + +func New() *Ownable2Step { + return &Ownable2Step{ + owner: std.PreviousRealm().Address(), + pendingOwner: "", + } +} + +func NewWithAddress(addr std.Address) *Ownable2Step { + return &Ownable2Step{ + owner: addr, + pendingOwner: "", + } +} + +// TransferOwnership initiate the transfer of the ownership to a new address by setting the PendingOwner +func (o *Ownable2Step) TransferOwnership(newOwner std.Address) error { + if !o.CallerIsOwner() { + return ErrUnauthorized + } + if !newOwner.IsValid() { + return ErrInvalidAddress + } + + o.pendingOwner = newOwner + return nil +} + +// AcceptOwnership accepts the pending ownership transfer +func (o *Ownable2Step) AcceptOwnership() error { + if o.pendingOwner.String() == "" { + return ErrNoPendingOwner + } + if std.PreviousRealm().Address() != o.pendingOwner { + return ErrPendingUnauthorized + } + + o.owner = o.pendingOwner + o.pendingOwner = "" + + return nil +} + +// DropOwnership removes the owner, effectively disabling any owner-related actions +// Top-level usage: disables all only-owner actions/functions, +// Embedded usage: behaves like a burn functionality, removing the owner from the struct +func (o *Ownable2Step) DropOwnership() error { + if !o.CallerIsOwner() { + return ErrUnauthorized + } + + prevOwner := o.owner + o.owner = "" + + std.Emit( + OwnershipTransferEvent, + "from", prevOwner.String(), + "to", "", + ) + + return nil +} + +// Owner returns the owner address from Ownable +func (o *Ownable2Step) Owner() std.Address { + return o.owner +} + +// PendingOwner returns the pending owner address from Ownable2Step +func (o *Ownable2Step) PendingOwner() std.Address { + return o.pendingOwner +} + +// CallerIsOwner checks if the caller of the function is the Realm's owner +func (o *Ownable2Step) CallerIsOwner() bool { + return std.PreviousRealm().Address() == o.owner +} + +// AssertCallerIsOwner panics if the caller is not the owner +func (o *Ownable2Step) AssertCallerIsOwner() { + if std.PreviousRealm().Address() != o.owner { + panic(ErrUnauthorized) + } +} diff --git a/examples/gno.land/p/oxtekgrinder/ownable2step/ownable_test.gno b/examples/gno.land/p/oxtekgrinder/ownable2step/ownable_test.gno new file mode 100644 index 00000000000..9d73855fed8 --- /dev/null +++ b/examples/gno.land/p/oxtekgrinder/ownable2step/ownable_test.gno @@ -0,0 +1,156 @@ +package ownable2step + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +var ( + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") +) + +func TestNew(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOriginCaller(alice) + + o := New() + got := o.Owner() + pendingOwner := o.PendingOwner() + + uassert.Equal(t, got, alice) + uassert.Equal(t, pendingOwner.String(), "") +} + +func TestNewWithAddress(t *testing.T) { + o := NewWithAddress(alice) + + got := o.Owner() + pendingOwner := o.PendingOwner() + + uassert.Equal(t, got, alice) + uassert.Equal(t, pendingOwner.String(), "") +} + +func TestInitiateTransferOwnership(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOriginCaller(alice) + + o := New() + + err := o.TransferOwnership(bob) + urequire.NoError(t, err) + + owner := o.Owner() + pendingOwner := o.PendingOwner() + + uassert.Equal(t, owner, alice) + uassert.Equal(t, pendingOwner, bob) +} + +func TestTransferOwnership(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOriginCaller(alice) + + o := New() + + err := o.TransferOwnership(bob) + urequire.NoError(t, err) + + owner := o.Owner() + pendingOwner := o.PendingOwner() + + uassert.Equal(t, owner, alice) + uassert.Equal(t, pendingOwner, bob) + + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOriginCaller(bob) + + err = o.AcceptOwnership() + urequire.NoError(t, err) + + owner = o.Owner() + pendingOwner = o.PendingOwner() + + uassert.Equal(t, owner, bob) + uassert.Equal(t, pendingOwner.String(), "") +} + +func TestCallerIsOwner(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOriginCaller(alice) + + o := New() + unauthorizedCaller := bob + + std.TestSetRealm(std.NewUserRealm(unauthorizedCaller)) + std.TestSetOriginCaller(unauthorizedCaller) + + uassert.False(t, o.CallerIsOwner()) +} + +func TestDropOwnership(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + + o := New() + + err := o.DropOwnership() + urequire.NoError(t, err, "DropOwnership failed") + + owner := o.Owner() + uassert.Empty(t, owner, "owner should be empty") +} + +// Errors + +func TestErrUnauthorized(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + std.TestSetOriginCaller(alice) + + o := New() + + std.TestSetRealm(std.NewUserRealm(bob)) + std.TestSetOriginCaller(bob) + + uassert.ErrorContains(t, o.TransferOwnership(alice), ErrUnauthorized.Error()) + uassert.ErrorContains(t, o.DropOwnership(), ErrUnauthorized.Error()) +} + +func TestErrInvalidAddress(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + + o := New() + + err := o.TransferOwnership("") + uassert.ErrorContains(t, err, ErrInvalidAddress.Error()) + + err = o.TransferOwnership("10000000001000000000100000000010000000001000000000") + uassert.ErrorContains(t, err, ErrInvalidAddress.Error()) +} + +func TestErrNoPendingOwner(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + + o := New() + + err := o.AcceptOwnership() + uassert.ErrorContains(t, err, ErrNoPendingOwner.Error()) +} + +func TestErrPendingUnauthorized(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(alice)) + + o := New() + + err := o.TransferOwnership(bob) + urequire.NoError(t, err) + + std.TestSetRealm(std.NewUserRealm(alice)) + + err = o.AcceptOwnership() + uassert.ErrorContains(t, err, ErrPendingUnauthorized.Error()) +} diff --git a/examples/gno.land/p/sunspirit/md/gno.mod b/examples/gno.land/p/sunspirit/md/gno.mod new file mode 100644 index 00000000000..caee634f66f --- /dev/null +++ b/examples/gno.land/p/sunspirit/md/gno.mod @@ -0,0 +1 @@ +module gno.land/p/sunspirit/md diff --git a/examples/gno.land/p/sunspirit/md/md.gno b/examples/gno.land/p/sunspirit/md/md.gno new file mode 100644 index 00000000000..965373bee85 --- /dev/null +++ b/examples/gno.land/p/sunspirit/md/md.gno @@ -0,0 +1,179 @@ +package md + +import ( + "strings" + + "gno.land/p/demo/ufmt" +) + +// Builder helps to build a Markdown string from individual elements +type Builder struct { + elements []string +} + +// NewBuilder creates a new Builder instance +func NewBuilder() *Builder { + return &Builder{} +} + +// Add adds a Markdown element to the builder +func (m *Builder) Add(md ...string) *Builder { + m.elements = append(m.elements, md...) + return m +} + +// Render returns the final Markdown string joined with the specified separator +func (m *Builder) Render(separator string) string { + return strings.Join(m.elements, separator) +} + +// Bold returns bold text for markdown +func Bold(text string) string { + return ufmt.Sprintf("**%s**", text) +} + +// Italic returns italicized text for markdown +func Italic(text string) string { + return ufmt.Sprintf("*%s*", text) +} + +// Strikethrough returns strikethrough text for markdown +func Strikethrough(text string) string { + return ufmt.Sprintf("~~%s~~", text) +} + +// H1 returns a level 1 header for markdown +func H1(text string) string { + return ufmt.Sprintf("# %s\n", text) +} + +// H2 returns a level 2 header for markdown +func H2(text string) string { + return ufmt.Sprintf("## %s\n", text) +} + +// H3 returns a level 3 header for markdown +func H3(text string) string { + return ufmt.Sprintf("### %s\n", text) +} + +// H4 returns a level 4 header for markdown +func H4(text string) string { + return ufmt.Sprintf("#### %s\n", text) +} + +// H5 returns a level 5 header for markdown +func H5(text string) string { + return ufmt.Sprintf("##### %s\n", text) +} + +// H6 returns a level 6 header for markdown +func H6(text string) string { + return ufmt.Sprintf("###### %s\n", text) +} + +// BulletList returns an bullet list for markdown +func BulletList(items []string) string { + var sb strings.Builder + for _, item := range items { + sb.WriteString(ufmt.Sprintf("- %s\n", item)) + } + return sb.String() +} + +// OrderedList returns an ordered list for markdown +func OrderedList(items []string) string { + var sb strings.Builder + for i, item := range items { + sb.WriteString(ufmt.Sprintf("%d. %s\n", i+1, item)) + } + return sb.String() +} + +// TodoList returns a list of todo items with checkboxes for markdown +func TodoList(items []string, done []bool) string { + var sb strings.Builder + + for i, item := range items { + checkbox := " " + if done[i] { + checkbox = "x" + } + sb.WriteString(ufmt.Sprintf("- [%s] %s\n", checkbox, item)) + } + return sb.String() +} + +// Blockquote returns a blockquote for markdown +func Blockquote(text string) string { + lines := strings.Split(text, "\n") + var sb strings.Builder + for _, line := range lines { + sb.WriteString(ufmt.Sprintf("> %s\n", line)) + } + + return sb.String() +} + +// InlineCode returns inline code for markdown +func InlineCode(code string) string { + return ufmt.Sprintf("`%s`", code) +} + +// CodeBlock creates a markdown code block +func CodeBlock(content string) string { + return ufmt.Sprintf("```\n%s\n```", content) +} + +// LanguageCodeBlock creates a markdown code block with language-specific syntax highlighting +func LanguageCodeBlock(language, content string) string { + return ufmt.Sprintf("```%s\n%s\n```", language, content) +} + +// LineBreak returns the specified number of line breaks for markdown +func LineBreak(count uint) string { + if count > 0 { + return strings.Repeat("\n", int(count)+1) + } + return "" +} + +// HorizontalRule returns a horizontal rule for markdown +func HorizontalRule() string { + return "---\n" +} + +// Link returns a hyperlink for markdown +func Link(text, url string) string { + return ufmt.Sprintf("[%s](%s)", text, url) +} + +// Image returns an image for markdown +func Image(altText, url string) string { + return ufmt.Sprintf("![%s](%s)", altText, url) +} + +// Footnote returns a footnote for markdown +func Footnote(reference, text string) string { + return ufmt.Sprintf("[%s]: %s", reference, text) +} + +// Paragraph wraps the given text in a Markdown paragraph +func Paragraph(content string) string { + return ufmt.Sprintf("%s\n", content) +} + +// MdTable is an interface for table types that can be converted to Markdown format +type MdTable interface { + String() string +} + +// Table takes any MdTable implementation and returns its markdown representation +func Table(table MdTable) string { + return table.String() +} + +// EscapeMarkdown escapes special markdown characters in a string +func EscapeMarkdown(text string) string { + return ufmt.Sprintf("``%s``", text) +} diff --git a/examples/gno.land/p/sunspirit/md/md_test.gno b/examples/gno.land/p/sunspirit/md/md_test.gno new file mode 100644 index 00000000000..529cc2535bb --- /dev/null +++ b/examples/gno.land/p/sunspirit/md/md_test.gno @@ -0,0 +1,175 @@ +package md + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/sunspirit/table" +) + +func TestNewBuilder(t *testing.T) { + mdBuilder := NewBuilder() + + uassert.Equal(t, len(mdBuilder.elements), 0, "Expected 0 elements") +} + +func TestAdd(t *testing.T) { + mdBuilder := NewBuilder() + + header := H1("Hi") + body := Paragraph("This is a test") + + mdBuilder.Add(header, body) + + uassert.Equal(t, len(mdBuilder.elements), 2, "Expected 2 element") + uassert.Equal(t, mdBuilder.elements[0], header, "Expected element %s, got %s", header, mdBuilder.elements[0]) + uassert.Equal(t, mdBuilder.elements[1], body, "Expected element %s, got %s", body, mdBuilder.elements[1]) +} + +func TestRender(t *testing.T) { + mdBuilder := NewBuilder() + + header := H1("Hello") + body := Paragraph("This is a test") + + seperator := "\n" + expected := header + seperator + body + + output := mdBuilder.Add(header, body).Render(seperator) + + uassert.Equal(t, output, expected, "Expected rendered string %s, got %s", expected, output) +} + +func Test_Bold(t *testing.T) { + uassert.Equal(t, Bold("Hello"), "**Hello**") +} + +func Test_Italic(t *testing.T) { + uassert.Equal(t, Italic("Hello"), "*Hello*") +} + +func Test_Strikethrough(t *testing.T) { + uassert.Equal(t, Strikethrough("Hello"), "~~Hello~~") +} + +func Test_H1(t *testing.T) { + uassert.Equal(t, H1("Header 1"), "# Header 1\n") +} + +func Test_H2(t *testing.T) { + uassert.Equal(t, H2("Header 2"), "## Header 2\n") +} + +func Test_H3(t *testing.T) { + uassert.Equal(t, H3("Header 3"), "### Header 3\n") +} + +func Test_H4(t *testing.T) { + uassert.Equal(t, H4("Header 4"), "#### Header 4\n") +} + +func Test_H5(t *testing.T) { + uassert.Equal(t, H5("Header 5"), "##### Header 5\n") +} + +func Test_H6(t *testing.T) { + uassert.Equal(t, H6("Header 6"), "###### Header 6\n") +} + +func Test_BulletList(t *testing.T) { + items := []string{"Item 1", "Item 2", "Item 3"} + result := BulletList(items) + expected := "- Item 1\n- Item 2\n- Item 3\n" + uassert.Equal(t, result, expected) +} + +func Test_OrderedList(t *testing.T) { + items := []string{"Item 1", "Item 2", "Item 3"} + result := OrderedList(items) + expected := "1. Item 1\n2. Item 2\n3. Item 3\n" + uassert.Equal(t, result, expected) +} + +func Test_TodoList(t *testing.T) { + items := []string{"Task 1", "Task 2"} + done := []bool{true, false} + result := TodoList(items, done) + expected := "- [x] Task 1\n- [ ] Task 2\n" + uassert.Equal(t, result, expected) +} + +func Test_Blockquote(t *testing.T) { + text := "This is a blockquote.\nIt has multiple lines." + result := Blockquote(text) + expected := "> This is a blockquote.\n> It has multiple lines.\n" + uassert.Equal(t, result, expected) +} + +func Test_InlineCode(t *testing.T) { + result := InlineCode("code") + uassert.Equal(t, result, "`code`") +} + +func Test_LanguageCodeBlock(t *testing.T) { + result := LanguageCodeBlock("python", "print('Hello')") + expected := "```python\nprint('Hello')\n```" + uassert.Equal(t, result, expected) +} + +func Test_CodeBlock(t *testing.T) { + result := CodeBlock("print('Hello')") + expected := "```\nprint('Hello')\n```" + uassert.Equal(t, result, expected) +} + +func Test_LineBreak(t *testing.T) { + result := LineBreak(2) + expected := "\n\n\n" + uassert.Equal(t, result, expected) + + result = LineBreak(0) + expected = "" + uassert.Equal(t, result, expected) +} + +func Test_HorizontalRule(t *testing.T) { + result := HorizontalRule() + uassert.Equal(t, result, "---\n") +} + +func Test_Link(t *testing.T) { + result := Link("Google", "http://google.com") + uassert.Equal(t, result, "[Google](http://google.com)") +} + +func Test_Image(t *testing.T) { + result := Image("Alt text", "http://image.url") + uassert.Equal(t, result, "![Alt text](http://image.url)") +} + +func Test_Footnote(t *testing.T) { + result := Footnote("1", "This is a footnote.") + uassert.Equal(t, result, "[1]: This is a footnote.") +} + +func Test_Paragraph(t *testing.T) { + result := Paragraph("This is a paragraph.") + uassert.Equal(t, result, "This is a paragraph.\n") +} + +func Test_Table(t *testing.T) { + tb, err := table.New([]string{"Header1", "Header2"}, [][]string{ + {"Row1Col1", "Row1Col2"}, + {"Row2Col1", "Row2Col2"}, + }) + uassert.NoError(t, err) + + result := Table(tb) + expected := "| Header1 | Header2 |\n| ---|---|\n| Row1Col1 | Row1Col2 |\n| Row2Col1 | Row2Col2 |\n" + uassert.Equal(t, result, expected) +} + +func Test_EscapeMarkdown(t *testing.T) { + result := EscapeMarkdown("- This is `code`") + uassert.Equal(t, result, "``- This is `code```") +} diff --git a/examples/gno.land/p/sunspirit/table/gno.mod b/examples/gno.land/p/sunspirit/table/gno.mod new file mode 100644 index 00000000000..1814c50b25d --- /dev/null +++ b/examples/gno.land/p/sunspirit/table/gno.mod @@ -0,0 +1 @@ +module gno.land/p/sunspirit/table diff --git a/examples/gno.land/p/sunspirit/table/table.gno b/examples/gno.land/p/sunspirit/table/table.gno new file mode 100644 index 00000000000..8c27516c962 --- /dev/null +++ b/examples/gno.land/p/sunspirit/table/table.gno @@ -0,0 +1,106 @@ +package table + +import ( + "strings" + + "gno.land/p/demo/ufmt" +) + +// Table defines the structure for a markdown table +type Table struct { + header []string + rows [][]string +} + +// Validate checks if the number of columns in each row matches the number of columns in the header +func (t *Table) Validate() error { + numCols := len(t.header) + for _, row := range t.rows { + if len(row) != numCols { + return ufmt.Errorf("row %v does not match header length %d", row, numCols) + } + } + return nil +} + +// New creates a new Table instance, ensuring the header and rows match in size +func New(header []string, rows [][]string) (*Table, error) { + t := &Table{ + header: header, + rows: rows, + } + + if err := t.Validate(); err != nil { + return nil, err + } + + return t, nil +} + +// Table returns a markdown string for the given Table +func (t *Table) String() string { + if err := t.Validate(); err != nil { + panic(err) + } + + var sb strings.Builder + + sb.WriteString("| " + strings.Join(t.header, " | ") + " |\n") + sb.WriteString("| " + strings.Repeat("---|", len(t.header)) + "\n") + + for _, row := range t.rows { + sb.WriteString("| " + strings.Join(row, " | ") + " |\n") + } + + return sb.String() +} + +// AddRow adds a new row to the table +func (t *Table) AddRow(row []string) error { + if len(row) != len(t.header) { + return ufmt.Errorf("row %v does not match header length %d", row, len(t.header)) + } + t.rows = append(t.rows, row) + return nil +} + +// AddColumn adds a new column to the table with the specified values +func (t *Table) AddColumn(header string, values []string) error { + if len(values) != len(t.rows) { + return ufmt.Errorf("values length %d does not match the number of rows %d", len(values), len(t.rows)) + } + + // Add the new header + t.header = append(t.header, header) + + // Add the new column values to each row + for i, value := range values { + t.rows[i] = append(t.rows[i], value) + } + return nil +} + +// RemoveRow removes a row from the table by its index +func (t *Table) RemoveRow(index int) error { + if index < 0 || index >= len(t.rows) { + return ufmt.Errorf("index %d is out of range", index) + } + t.rows = append(t.rows[:index], t.rows[index+1:]...) + return nil +} + +// RemoveColumn removes a column from the table by its index +func (t *Table) RemoveColumn(index int) error { + if index < 0 || index >= len(t.header) { + return ufmt.Errorf("index %d is out of range", index) + } + + // Remove the column from the header + t.header = append(t.header[:index], t.header[index+1:]...) + + // Remove the corresponding column from each row + for i := range t.rows { + t.rows[i] = append(t.rows[i][:index], t.rows[i][index+1:]...) + } + return nil +} diff --git a/examples/gno.land/p/sunspirit/table/table_test.gno b/examples/gno.land/p/sunspirit/table/table_test.gno new file mode 100644 index 00000000000..d4cd56ad0a8 --- /dev/null +++ b/examples/gno.land/p/sunspirit/table/table_test.gno @@ -0,0 +1,146 @@ +package table + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestNew(t *testing.T) { + header := []string{"Name", "Age", "Country"} + rows := [][]string{ + {"Alice", "30", "USA"}, + {"Bob", "25", "UK"}, + } + + table, err := New(header, rows) + urequire.NoError(t, err) + + uassert.Equal(t, len(header), len(table.header)) + uassert.Equal(t, len(rows), len(table.rows)) +} + +func Test_AddRow(t *testing.T) { + header := []string{"Name", "Age"} + rows := [][]string{ + {"Alice", "30"}, + {"Bob", "25"}, + } + + table, err := New(header, rows) + urequire.NoError(t, err) + + // Add a valid row + err = table.AddRow([]string{"Charlie", "28"}) + urequire.NoError(t, err) + + expectedRows := [][]string{ + {"Alice", "30"}, + {"Bob", "25"}, + {"Charlie", "28"}, + } + uassert.Equal(t, len(expectedRows), len(table.rows)) + + // Attempt to add a row with a different number of columns + err = table.AddRow([]string{"David"}) + uassert.Error(t, err) +} + +func Test_AddColumn(t *testing.T) { + header := []string{"Name", "Age"} + rows := [][]string{ + {"Alice", "30"}, + {"Bob", "25"}, + } + + table, err := New(header, rows) + urequire.NoError(t, err) + + // Add a valid column + err = table.AddColumn("Country", []string{"USA", "UK"}) + urequire.NoError(t, err) + + expectedHeader := []string{"Name", "Age", "Country"} + expectedRows := [][]string{ + {"Alice", "30", "USA"}, + {"Bob", "25", "UK"}, + } + uassert.Equal(t, len(expectedHeader), len(table.header)) + uassert.Equal(t, len(expectedRows), len(table.rows)) + + // Attempt to add a column with a different number of values + err = table.AddColumn("City", []string{"New York"}) + uassert.Error(t, err) +} + +func Test_RemoveRow(t *testing.T) { + header := []string{"Name", "Age", "Country"} + rows := [][]string{ + {"Alice", "30", "USA"}, + {"Bob", "25", "UK"}, + } + + table, err := New(header, rows) + urequire.NoError(t, err) + + // Remove the first row + err = table.RemoveRow(0) + urequire.NoError(t, err) + + expectedRows := [][]string{ + {"Bob", "25", "UK"}, + } + uassert.Equal(t, len(expectedRows), len(table.rows)) + + // Attempt to remove a row out of range + err = table.RemoveRow(5) + uassert.Error(t, err) +} + +func Test_RemoveColumn(t *testing.T) { + header := []string{"Name", "Age", "Country"} + rows := [][]string{ + {"Alice", "30", "USA"}, + {"Bob", "25", "UK"}, + } + + table, err := New(header, rows) + urequire.NoError(t, err) + + // Remove the second column (Age) + err = table.RemoveColumn(1) + urequire.NoError(t, err) + + expectedHeader := []string{"Name", "Country"} + expectedRows := [][]string{ + {"Alice", "USA"}, + {"Bob", "UK"}, + } + uassert.Equal(t, len(expectedHeader), len(table.header)) + uassert.Equal(t, len(expectedRows), len(table.rows)) + + // Attempt to remove a column out of range + err = table.RemoveColumn(5) + uassert.Error(t, err) +} + +func Test_Validate(t *testing.T) { + header := []string{"Name", "Age", "Country"} + rows := [][]string{ + {"Alice", "30", "USA"}, + {"Bob", "25"}, + } + + table, err := New(header, rows[:1]) + urequire.NoError(t, err) + + // Validate should pass + err = table.Validate() + urequire.NoError(t, err) + + // Add an invalid row and validate again + table.rows = append(table.rows, rows[1]) + err = table.Validate() + uassert.Error(t, err) +} diff --git a/examples/gno.land/p/thox/accesscontrol/accesscontrol.gno b/examples/gno.land/p/thox/accesscontrol/accesscontrol.gno new file mode 100644 index 00000000000..5785464d511 --- /dev/null +++ b/examples/gno.land/p/thox/accesscontrol/accesscontrol.gno @@ -0,0 +1,204 @@ +package accesscontrol + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ownable" +) + +const ( + RoleCreatedEvent = "RoleCreated" + RoleGrantedEvent = "RoleGranted" + RoleRevokedEvent = "RoleRevoked" + RoleRenouncedEvent = "RoleRenounced" + RoleSetEvent = "RoleSet" +) + +// Role struct to store role information +type Role struct { + Name string + Holders *avl.Tree // std.Address -> struct{} + Ownable *ownable.Ownable +} + +// Roles struct to store all Roles information +type Roles struct { + Roles []*Role + UserToRoles avl.Tree // std.Address -> []*Role + Ownable *ownable.Ownable +} + +func validRoleName(name string) error { + if len(name) > 30 || name == "" { + return ErrNameRole + } + return nil +} + +// NewRole creates a new instance of Role +func NewRole(name string, admin std.Address) (*Role, error) { + if err := validRoleName(name); err != nil { + return nil, ErrNameRole + } + + return &Role{ + Name: name, + Holders: avl.NewTree(), + Ownable: ownable.NewWithAddress(admin), + }, nil +} + +// CreateRole create a new role within the realm +func (rs *Roles) CreateRole(name string) (*Role, error) { + if err := validRoleName(name); err != nil { + return nil, ErrNameRole + } + + if !rs.Ownable.CallerIsOwner() { + return nil, ErrNotOwner + } + + for _, role := range rs.Roles { + if role.Name == name { + return nil, ErrRoleSameName + } + } + + role, err := NewRole(name, rs.Ownable.Owner()) + if err != nil { + return nil, err + } + + rs.Roles = append(rs.Roles, role) + + std.Emit( + RoleCreatedEvent, + "roleName", name, + "sender", rs.Ownable.Owner().String(), + ) + + return role, nil +} + +// HasAccount check if an account has a specific role +func (r *Role) HasAccount(account std.Address) bool { + return r.Holders.Has(account.String()) +} + +// FindRole searches for a role by its name +func (rs *Roles) FindRole(name string) (*Role, error) { + for _, role := range rs.Roles { + if role.Name == name { + return role, nil + } + } + + return nil, ErrRoleNotFound +} + +// GrantRole grants a role to an account +func (rs *Roles) GrantRole(name string, account std.Address) error { + r, err := rs.FindRole(name) + if err != nil { + return ErrRoleNotFound + } + + if !r.Ownable.CallerIsOwner() { + return ErrNotOwner + } + + r.Holders.Set(account.String(), struct{}{}) + + // Add in UserToRoles + roles, found := rs.UserToRoles.Get(account.String()) + if !found { + roles = []*Role{} + } + roles = append(roles.([]*Role), r) + rs.UserToRoles.Set(account.String(), roles) + + std.Emit( + RoleGrantedEvent, + "roleName", r.Name, + "account", account.String(), + "sender", std.PreviousRealm().Address().String(), + ) + + return nil +} + +// RevokeRole revokes a role from an account +func (rs *Roles) RevokeRole(name string, account std.Address) error { + r, err := rs.FindRole(name) + if err != nil { + return ErrRoleNotFound + } + + if !r.Ownable.CallerIsOwner() { + return ErrNotOwner + } + + r.Holders.Remove(account.String()) + + // Remove in UserToRoles + roles, found := rs.UserToRoles.Get(account.String()) + if found { + updatedRoles := []*Role{} + for _, role := range roles.([]*Role) { + if role != r { + updatedRoles = append(updatedRoles, role) + } + } + rs.UserToRoles.Set(account.String(), updatedRoles) + } + + std.Emit( + RoleRevokedEvent, + "roleName", r.Name, + "account", account.String(), + "sender", std.PreviousRealm().Address().String(), + ) + + return nil +} + +// RenounceRole allows an account to renounce a role it holds +func (rs *Roles) RenounceRole(name string) error { + r, err := rs.FindRole(name) + if err != nil { + return ErrRoleNotFound + } + + caller := std.OriginCaller() + + if !r.HasAccount(caller) { + return ErrAccountNotRole + } + + r.Holders.Remove(caller.String()) + + std.Emit( + RoleRenouncedEvent, + "roleName", r.Name, + "account", caller.String(), + "sender", caller.String(), + ) + + return nil +} + +// SetRoleAdmin transfers the ownership of the role to a new administrator +func (r *Role) SetRoleAdmin(admin std.Address) error { + if err := r.Ownable.TransferOwnership(admin); err != nil { + return err + } + + std.Emit( + RoleSetEvent, + "roleName", r.Name, + "newAdminRole", r.Ownable.Owner().String(), + ) + + return nil +} diff --git a/examples/gno.land/p/thox/accesscontrol/accesscontrol_test.gno b/examples/gno.land/p/thox/accesscontrol/accesscontrol_test.gno new file mode 100644 index 00000000000..447d2f2ae10 --- /dev/null +++ b/examples/gno.land/p/thox/accesscontrol/accesscontrol_test.gno @@ -0,0 +1,244 @@ +package accesscontrol + +import ( + "std" + "testing" + + "gno.land/p/demo/ownable" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +var ( + admin = testutils.TestAddress("admin1") + newAdmin = testutils.TestAddress("admin2") + user1 = testutils.TestAddress("user1") + user2 = testutils.TestAddress("user2") + + roleName = "TestRole" +) + +func initSetup(admin std.Address) *Roles { + return &Roles{ + Roles: []*Role{}, + Ownable: ownable.NewWithAddress(admin), + } +} + +func TestCreateRole(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + role, err := roles.CreateRole(roleName) + uassert.NoError(t, err) + uassert.True(t, role != nil, "role should not be nil") + uassert.Equal(t, role.Name, roleName) + + _, err = roles.CreateRole(roleName) + uassert.Error(t, err, "should fail on duplicate role creation") +} + +func TestGrantRole(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + _, err := roles.CreateRole(roleName) + uassert.NoError(t, err) + + err = roles.GrantRole(roleName, user1) + uassert.NoError(t, err) + + role, err := roles.FindRole(roleName) + uassert.NoError(t, err) + uassert.True(t, role.HasAccount(user1), "user1 should have the TestRole") + + rolesList, found := roles.UserToRoles.Get(user1.String()) + uassert.True(t, found, "user1 should be in UserToRoles") + uassert.True(t, containsRole(rolesList.([]*Role), role), "UserToRoles should contain TestRole for user1") +} + +func TestGrantRoleByNonOwner(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + _, err := roles.CreateRole(roleName) + uassert.NoError(t, err) + + std.TestSetOriginCaller(user2) + roles.Ownable.TransferOwnership(user2) + err = roles.GrantRole(roleName, user1) + uassert.Error(t, err, "non-owner should not be able to grant roles") + + roles.Ownable.TransferOwnership(admin) +} + +func TestRevokeRole(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + _, err := roles.CreateRole(roleName) + uassert.NoError(t, err) + err = roles.GrantRole(roleName, user1) + uassert.NoError(t, err) + + err = roles.RevokeRole(roleName, user1) + uassert.NoError(t, err) + + role, err := roles.FindRole(roleName) + uassert.NoError(t, err) + uassert.False(t, role.HasAccount(user1), "user1 should no longer have the TestRole") + + rolesList, found := roles.UserToRoles.Get(user1.String()) + if found { + uassert.False(t, containsRole(rolesList.([]*Role), role), "UserToRoles should not contain TestRole for user1 after revocation") + } +} + +func TestRenounceRole(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + role, err := roles.CreateRole(roleName) + uassert.NoError(t, err) + err = roles.GrantRole(roleName, user1) + uassert.NoError(t, err) + + // Pas besoin de transférer la propriété pour renoncer à un rôle + std.TestSetOriginCaller(user1) + err = roles.RenounceRole(roleName) + uassert.NoError(t, err) + + role, err = roles.FindRole(roleName) + uassert.NoError(t, err) + uassert.False(t, role.HasAccount(user1), "user1 should have renounced the TestRole") +} + +func TestSetRoleAdmin(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + role, err := roles.CreateRole(roleName) + uassert.NoError(t, err) + + err = role.SetRoleAdmin(newAdmin) + uassert.NoError(t, err, "admin change should succeed") + + std.TestSetOriginCaller(newAdmin) + uassert.Equal(t, role.Ownable.Owner(), newAdmin, "the new admin should be newAdmin") + + std.TestSetOriginCaller(admin) + uassert.NotEqual(t, role.Ownable.Owner(), admin, "the old admin should no longer be the owner") +} + +func TestCreateRoleInvalidName(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + _, err := roles.CreateRole("") + uassert.Error(t, err, "should fail on empty role name") + + longRoleName := "thisisaverylongrolenamethatexceedsthenormallimitfortestingpurposes" + _, err = roles.CreateRole(longRoleName) + uassert.Error(t, err, "should fail on very long role name") +} + +func TestRevokeRoleByNonOwner(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + _, err := roles.CreateRole(roleName) + uassert.NoError(t, err) + err = roles.GrantRole(roleName, user1) + uassert.NoError(t, err) + + std.TestSetOriginCaller(user2) + err = roles.RevokeRole(roleName, user1) + uassert.Error(t, err, "non-owner should not be able to revoke roles") +} + +func TestGrantRoleToNonExistentRole(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + err := roles.GrantRole("NonExistentRole", user1) + uassert.Error(t, err, "should fail when granting non-existent role") +} + +func TestRevokeRoleFromNonExistentRole(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + err := roles.RevokeRole("NonExistentRole", user1) + uassert.Error(t, err, "should fail when revoking non-existent role") +} + +func TestRenounceNonExistentRole(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(user1) + + err := roles.RenounceRole("NonExistentRole") + uassert.Error(t, err, "should fail when renouncing non-existent role") +} + +func TestDeleteRole(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + role, err := roles.CreateRole(roleName) + uassert.NoError(t, err) + uassert.True(t, role != nil, "role should not be nil") + + roles.Roles = []*Role{} // Clear roles for testing purpose + _, err = roles.FindRole(roleName) + uassert.Error(t, err, "should fail when trying to find deleted role") +} + +func TestUserToRolesWithMultipleRoles(t *testing.T) { + roles := initSetup(admin) + + std.TestSetOriginCaller(admin) + + roleName1 := "Role1" + roleName2 := "Role2" + + // Create two roles + _, err := roles.CreateRole(roleName1) + uassert.NoError(t, err) + _, err = roles.CreateRole(roleName2) + uassert.NoError(t, err) + + // Grant both roles to user1 + err = roles.GrantRole(roleName1, user1) + uassert.NoError(t, err) + err = roles.GrantRole(roleName2, user1) + uassert.NoError(t, err) + + // Check if user1 has both roles + rolesList, found := roles.UserToRoles.Get(user1.String()) + uassert.True(t, found, "user1 should be in UserToRoles") + role1, _ := roles.FindRole(roleName1) + role2, _ := roles.FindRole(roleName2) + uassert.True(t, containsRole(rolesList.([]*Role), role1), "UserToRoles should contain Role1 for user1") + uassert.True(t, containsRole(rolesList.([]*Role), role2), "UserToRoles should contain Role2 for user1") +} + +// func test for check if a role is in a list of roles +func containsRole(roles []*Role, target *Role) bool { + for _, role := range roles { + if role == target { + return true + } + } + return false +} diff --git a/examples/gno.land/p/thox/accesscontrol/doc.gno b/examples/gno.land/p/thox/accesscontrol/doc.gno new file mode 100644 index 00000000000..c6ba2f79f88 --- /dev/null +++ b/examples/gno.land/p/thox/accesscontrol/doc.gno @@ -0,0 +1,59 @@ +// Package accesscontrol implements a role-based access control (RBAC) system for Gno applications. +// It provides functionality to create, assign, revoke, and transfer roles. +// +// # Usage +// +// Import the `gno.land/p/demo/accesscontrol` package to manage roles within your Gno realm. You can create roles, +// assign them to users, revoke them, and transfer role ownership. +// +// Roles can be created by the contract owner using `CreateRole`. Each role is uniquely identified by its name. +// +// CreateRole("editor") +// +// Use `GrantRole` to assign a role to a user, and `RevokeRole` to remove a role from a user. +// +// GrantRole("editor", userAddress) +// +// RevokeRole("editor", userAddress) +// +// Users can renounce their roles using `RenounceRole`, voluntarily removing themselves from a role. +// +// RenounceRole("editor") +// +// You can look up a role by name with `FindRole`. +// +// FindRole("editor") +// +// Role ownership can be transferred using `SetRoleAdmin`. +// +// SetRoleAdmin(newAdminAddress) +// +// Key events +// - `RoleCreatedEvent`: Triggered when a new role is created +// Key includes: +// `roleName` (name of the role) +// `sender` (address of the sender) +// +// - `RoleGrantedEvent`: Triggered when a role is granted to an account +// Key includes: +// `roleName` (name of the role) +// `account` (address of the account) +// `sender` (address of the sender) +// +// - `RoleRevokedEvent`: Triggered when a role is revoked from an account +// Key includes: +// `roleName` (name of the role) +// `account` (address of the account) +// `sender` (address of the sender) +// +// - `RoleRenouncedEvent`: Triggered when a role is renounced by an account +// Key includes: +// `roleName` (name of the role) +// `account` (address of the account) +// +// - `RoleSetEvent`: Triggered when a role's administrator is set or changed +// Key includes: +// `roleName` (name of the role) +// `newAdmin` (address of the new administrator) +// `sender` (address of the sender) +package accesscontrol diff --git a/examples/gno.land/p/thox/accesscontrol/errors.gno b/examples/gno.land/p/thox/accesscontrol/errors.gno new file mode 100644 index 00000000000..7f4261345eb --- /dev/null +++ b/examples/gno.land/p/thox/accesscontrol/errors.gno @@ -0,0 +1,12 @@ +package accesscontrol + +import "errors" + +var ( + ErrNotMatchAccount = errors.New("accesscontrol: caller confirmation does not match account") + ErrRoleSameName = errors.New("accesscontrol: role already exists with the same name") + ErrRoleNotFound = errors.New("accesscontrol: role not found") + ErrAccountNotRole = errors.New("accesscontrol: this account does not have the role") + ErrNameRole = errors.New("accesscontrol: role name cannot be empty or exceed 30 characters") + ErrNotOwner = errors.New("accesscontrol: caller is not the owner of the role") +) diff --git a/examples/gno.land/p/thox/accesscontrol/gno.mod b/examples/gno.land/p/thox/accesscontrol/gno.mod new file mode 100644 index 00000000000..717b3244e63 --- /dev/null +++ b/examples/gno.land/p/thox/accesscontrol/gno.mod @@ -0,0 +1 @@ +module gno.land/p/thox/accesscontrol diff --git a/examples/gno.land/p/thox/timelock/doc.gno b/examples/gno.land/p/thox/timelock/doc.gno new file mode 100644 index 00000000000..025d93ecc17 --- /dev/null +++ b/examples/gno.land/p/thox/timelock/doc.gno @@ -0,0 +1,32 @@ +// Package timelock provides a library for scheduling, cancelling, and +// executing time-locked operations in Gno. It ensures that +// operations are only carried out after a specified delay and offers +// mechanisms for managing and verifying the status of these operations. +// This package leverages an AVL tree for efficient management of timestamps +// and integrates role-based access control for administrative tasks. +// +// # Usage: +// +// import "gno.land/p/demo/timelock" +// import "gno.land/p/demo/accesscontrol" +// +// Initialize timelock utility with an AVL tree and access control. +// timestamps := avl.NewTree() +// adminRole := accesscontrol.NewRole("admin", std.Address("admin-address")) +// timeLockUtil := timelock.NewTimeLockUtil(timestamps, adminRole, 30) +// +// Schedule an operation with a delay of 60 seconds. +// id := seqid.ID() +// timeLockUtil.Schedule(id, 60) +// +// Check if an operation is pending. +// isPending := timeLockUtil.IsPending(id) +// +// Execute the operation when it is pending. +// if timeLockUtil.IsPending(id) { +// timeLockUtil.Execute(id) +// } +// +// Update the minimum delay for future operations. +// timeLockUtil.UpdateDelay(45) +package timelock diff --git a/examples/gno.land/p/thox/timelock/errors.gno b/examples/gno.land/p/thox/timelock/errors.gno new file mode 100644 index 00000000000..4027dab5890 --- /dev/null +++ b/examples/gno.land/p/thox/timelock/errors.gno @@ -0,0 +1,14 @@ +package timelock + +import "errors" + +var ( + ErrNilTimestampsOrAccessControl = errors.New("timelock: timestamps and accesscontrol values must be different from nil") + ErrInsufficientDelay = errors.New("timelockutil: Schedule: insufficient delay") + ErrOperationAlreadyScheduled = errors.New("timelockutil: Schedule: operation already scheduled") + ErrOperationNotPending = errors.New("timelock: operation not pending") + ErrUnexpectedType = errors.New("timelockutil: GetTimestamp: unexpected type") + ErrUpadateDelay = errors.New("timelock: UpdateDelay: only admin can update delay") + ErrOperationCancelNotPending = errors.New("timelock: Cancel: operation not pending") + ErrOperationExecuteNotPending = errors.New("timelock: Execute: operation not pending") +) diff --git a/examples/gno.land/p/thox/timelock/gno.mod b/examples/gno.land/p/thox/timelock/gno.mod new file mode 100644 index 00000000000..3c26853bece --- /dev/null +++ b/examples/gno.land/p/thox/timelock/gno.mod @@ -0,0 +1 @@ +module gno.land/p/thox/timelock diff --git a/examples/gno.land/p/thox/timelock/timelock.gno b/examples/gno.land/p/thox/timelock/timelock.gno new file mode 100644 index 00000000000..49a93443ba0 --- /dev/null +++ b/examples/gno.land/p/thox/timelock/timelock.gno @@ -0,0 +1,205 @@ +package timelock + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" + "gno.land/p/thox/accesscontrol" +) + +// Represents the status of a planned operation +type OperationState int + +const ( + Unset OperationState = iota + Pending + Ready + Done +) + +func (os OperationState) StateToString() string { + switch os { + case Unset: + return "Unset" + case Pending: + return "Pending" + case Ready: + return "Ready" + case Done: + return "Done" + default: + return "Unknown" + } +} + +// OperationStatus represents the status of an operation +type OperationStatus struct { + sheduleTime int64 + isDone bool +} + +// TimeLock stores the necessary parameters for the timelock operations +type TimeLock struct { + timestamps *avl.Tree // id -> time.Time + accessControl *accesscontrol.Role + minDelay uint64 +} + +// New instance of TimeLock +func NewTimeLock(timestamps *avl.Tree, accessControl *accesscontrol.Role, minDelay uint64) (*TimeLock, error) { + if timestamps == nil || accessControl == nil { + return nil, ErrNilTimestampsOrAccessControl + } + + return &TimeLock{ + timestamps: timestamps, + accessControl: accessControl, + minDelay: minDelay, + }, nil +} + +// Schedules an operation to be carried out after a minimum delay +func (tl *TimeLock) Schedule(id seqid.ID, delay uint64) error { + if delay < tl.minDelay { + return ErrInsufficientDelay + } + + if tl.timestamps.Has(id.Binary()) { + return ErrOperationAlreadyScheduled + } + + timestamp := time.Now().Unix() + int64(delay) + status := OperationStatus{sheduleTime: timestamp, isDone: false} + tl.timestamps.Set(id.Binary(), status) + + std.Emit( + "TimeLockScheduled", + "id", id.String(), + "delay", strconv.FormatInt(int64(delay), 10), + ) + + return nil +} + +// Remove operation +func (tl *TimeLock) Remove(id seqid.ID) { + tl.timestamps.Remove(id.Binary()) + + std.Emit( + "TimeLockRemoved", + "id", id.String(), + ) +} + +// Cancels a planned operation +func (tl *TimeLock) Cancel(id seqid.ID) error { + if !tl.IsPending(id) { + return ErrOperationCancelNotPending + } + + tl.timestamps.Remove(id.Binary()) + + std.Emit( + "TimeLockCancelled", + "id", id.String(), + ) + return nil +} + +// Executes a pending operation +func (tl *TimeLock) Execute(id seqid.ID) error { + if !tl.IsPending(id) { + return ErrOperationExecuteNotPending + } + + status, err := tl.GetOperationStatus(id) + if err != nil { + return err + } + status.isDone = true + tl.timestamps.Set(id.Binary(), status) + + std.Emit( + "TimeLockExecuted", + "id", id.String(), + ) + + return nil +} + +// Update the minimum lead time for future operations +func (tl *TimeLock) UpdateDelay(newDelay uint64) error { + if std.PreviousRealm().Address() != tl.accessControl.Ownable.Owner() { + return ErrUpadateDelay + } + + std.Emit( + "TimeLockMinDelayChanged", + "oldDelay", strconv.FormatInt(int64(tl.minDelay), 10), + "newDelay", strconv.FormatInt(int64(newDelay), 10), + ) + + tl.minDelay = newDelay + + return nil +} + +// Checks if an operation is pending +func (tl *TimeLock) IsPending(id seqid.ID) bool { + state, err := tl.GetOperationState(id) + if err != nil { + // Handle the error appropriately; for now, we assume the operation is not pending if there's an error + ufmt.Errorf("Error retrieving operation state: %v", err) + return false + } + + return state == Pending +} + +// Checks if an operation is ready +func (tl *TimeLock) IsReady(id seqid.ID) bool { + state, err := tl.GetOperationState(id) + if err != nil { + // Handle the error appropriately; for now, we assume the operation is not pending if there's an error + ufmt.Errorf("Error retrieving operation state: %v", err) + return false + } + + return state == Ready +} + +// Returns the status of an operation +func (tl *TimeLock) GetOperationState(id seqid.ID) (OperationState, error) { + status, err := tl.GetOperationStatus(id) + if err != nil { + return Unset, err + } + if status.isDone { + return Done, nil + } + if status.sheduleTime == 0 { + return Unset, nil + } + if status.sheduleTime > time.Now().Unix() { + return Pending, nil + } + return Ready, nil +} + +// Returns the status of an operation +func (tl *TimeLock) GetOperationStatus(id seqid.ID) (OperationStatus, error) { + value, ok := tl.timestamps.Get(id.Binary()) + + if !ok { + return OperationStatus{}, nil // Return an empty status if the operation is not found + } + if status, ok := value.(OperationStatus); ok { + return status, nil + } else { + return OperationStatus{}, ErrUnexpectedType + } +} diff --git a/examples/gno.land/p/thox/timelock/timelock_test.gno b/examples/gno.land/p/thox/timelock/timelock_test.gno new file mode 100644 index 00000000000..207e875ef4d --- /dev/null +++ b/examples/gno.land/p/thox/timelock/timelock_test.gno @@ -0,0 +1,89 @@ +package timelock + +import ( + "std" + "testing" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" + "gno.land/p/demo/uassert" + "gno.land/p/thox/accesscontrol" +) + +func TestTimelock(t *testing.T) { + // Initialization + timestamps := avl.NewTree() + minDelay := uint64(2) // 2 seconds to simplify testing + accessControl, _ := accesscontrol.NewRole("admin", std.OriginCaller()) + timelockUtil, err := NewTimeLock(timestamps, accessControl, minDelay) + + // Generate a new ID from time.Now().UnixNano() with seconds added to guarantee uniqueness + newID := func(offset int64) seqid.ID { + return seqid.ID(time.Now().UnixNano() + offset) + } + + uassert.NoError(t, err, "Failed to create TimeLock instance") + + // Test Schedule + t.Run("Schedule", func(t *testing.T) { + id := newID(0) + delay := uint64(3) // 3 seconds + + err := timelockUtil.Schedule(id, delay) + + uassert.NoError(t, err, "Schedule failed") + + status, err := timelockUtil.GetOperationStatus(id) + + uassert.NoError(t, err, "failed to get operation status") + uassert.NotEmpty(t, status.sheduleTime, "operation status not set or invalid") + }) + + // Test Cancel + t.Run("Cancel", func(t *testing.T) { + id := newID(1) + + // Plan a new operation to ensure it is unique + err := timelockUtil.Schedule(id, uint64(3)) + uassert.NoError(t, err, "Failed to schedule operation for cancellation") + + err = timelockUtil.Cancel(id) + uassert.NoError(t, err, "Cancel failed") + + status, err := timelockUtil.GetOperationStatus(id) + uassert.NoError(t, err, "failed to get operation status") + uassert.Empty(t, status.sheduleTime, "operation not cancelled") + }) + + // Test Execute + t.Run("Execute", func(t *testing.T) { + id := newID(2) + delay := uint64(3) // 3 seconds + futureTime := time.Now().Unix() + int64(delay) + + // Schedule the operation with a future timestamp + err := timelockUtil.Schedule(id, delay) + uassert.NoError(t, err, "Failed to schedule operation for execution") + + // Simulates the passage of time by setting the timestamp to a future time + timestamps.Set(id.Binary(), OperationStatus{sheduleTime: futureTime, isDone: false}) + + err = timelockUtil.Execute(id) + uassert.NoError(t, err, "Execute failed") + + state, err := timelockUtil.GetOperationState(id) + uassert.NoError(t, err, "failed to get operation state") + uassert.Equal(t, Done.StateToString(), state.StateToString(), "operation not executed") + }) + + // Test UpdateDelay + t.Run("UpdateDelay", func(t *testing.T) { + newDelay := uint64(4) // 4 seconds + + err := timelockUtil.UpdateDelay(newDelay) + uassert.NoError(t, err, "UpdateDelay failed") + + uassert.Equal(t, newDelay, timelockUtil.minDelay, "minDelay not updated") + }) +} diff --git a/examples/gno.land/p/wyhaines/rand/isaac/isaac_test.gno b/examples/gno.land/p/wyhaines/rand/isaac/isaac_test.gno index b08621e271c..fdf633bb543 100644 --- a/examples/gno.land/p/wyhaines/rand/isaac/isaac_test.gno +++ b/examples/gno.land/p/wyhaines/rand/isaac/isaac_test.gno @@ -14,7 +14,7 @@ type OpenISAAC struct { } func TestISAACSeeding(t *testing.T) { - isaac := New() + _ = New() } func TestISAACRand(t *testing.T) { diff --git a/examples/gno.land/p/wyhaines/rand/isaac64/isaac64_test.gno b/examples/gno.land/p/wyhaines/rand/isaac64/isaac64_test.gno index 239e7f818fb..74abf2c13ca 100644 --- a/examples/gno.land/p/wyhaines/rand/isaac64/isaac64_test.gno +++ b/examples/gno.land/p/wyhaines/rand/isaac64/isaac64_test.gno @@ -14,7 +14,7 @@ type OpenISAAC struct { } func TestISAACSeeding(t *testing.T) { - isaac := New() + _ = New() } func TestISAACRand(t *testing.T) { diff --git a/examples/gno.land/r/agherasie/forms/forms.gno b/examples/gno.land/r/agherasie/forms/forms.gno new file mode 100644 index 00000000000..a4c235ff3e9 --- /dev/null +++ b/examples/gno.land/r/agherasie/forms/forms.gno @@ -0,0 +1,118 @@ +package forms + +import ( + "std" + + "gno.land/p/agherasie/forms" + "gno.land/p/demo/json" + "gno.land/p/demo/ufmt" + "gno.land/r/leon/hof" +) + +var db *forms.FormDB + +func init() { + hof.Register() + db = forms.NewDB() +} + +func CreateForm(title string, description string, openAt string, closeAt string, data string) string { + id, err := db.CreateForm(title, description, openAt, closeAt, data) + if err != nil { + panic(err) + } + return id +} + +func GetForms() string { + builder := forms.FormNodeBuilder{json.Builder()} + + builder.WriteArray("forms", func(builder *forms.FormArrayBuilder) { + for _, form := range db.Forms { + builder.WriteObject(func(builder *forms.FormNodeBuilder) { + builder.WriteForm("form", form) + }) + } + }) + + encoded, err := json.Marshal(builder.Node()) + if err != nil { + panic(err) + } + + return string(encoded) +} + +func GetFormByID(id string) string { + form, err := db.GetForm(id) + if err != nil { + panic(err) + } + + builder := forms.FormNodeBuilder{json.Builder()} + + builder.WriteForm("form", form). + WriteObject("submissions", func(builder *forms.FormNodeBuilder) { + formSubmissions := db.GetSubmissionsByFormID(form.ID) + for _, submission := range formSubmissions { + builder.WriteFormSubmission(submission.Author.String(), submission) + } + }) + + openAt, err := form.OpenAt() + if err == nil { + builder.WriteString("openAt", openAt.Format("2006-01-02 15:04:05")) + } + closeAt, err := form.CloseAt() + if err == nil { + builder.WriteString("closeAt", closeAt.Format("2006-01-02 15:04:05")) + } + + encoded, err := json.Marshal(builder.Node()) + if err != nil { + panic(err) + } + + return string(encoded) +} + +func GetAnswer(formID string, authorID string) string { + _, err := db.GetForm(formID) + if err != nil { + panic(err) + } + + answer, err := db.GetAnswer(formID, std.Address(authorID)) + if answer != nil { + panic(err) + } + + return answer.Answers +} + +func SubmitForm(formID string, answers string) { + _, err := db.GetForm(formID) + if err != nil { + panic(err) + } + + db.SubmitForm(formID, answers) +} + +func Render(path string) string { + if len(db.Forms) == 0 { + response := "No forms yet !" + return response + } + + response := "Forms:\n\n" + for _, form := range db.Forms { + response += ufmt.Sprintf("- %s\n\n", GetFormByID(form.ID)) + } + response += "Answers:\n\n" + for _, answer := range db.Answers { + response += ufmt.Sprintf("- Form ID: %s\nAuthor: %s\nSubmitted At: %s\n>Answers: %s\n\n", answer.FormID, answer.Author, answer.SubmittedAt, answer.Answers) + } + + return response +} diff --git a/examples/gno.land/r/agherasie/forms/forms_test.gno b/examples/gno.land/r/agherasie/forms/forms_test.gno new file mode 100644 index 00000000000..8011719bd4d --- /dev/null +++ b/examples/gno.land/r/agherasie/forms/forms_test.gno @@ -0,0 +1,48 @@ +package forms + +import ( + "std" + "strings" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" +) + +func TestGetFormByID(t *testing.T) { + alice := testutils.TestAddress("alice") + std.TestSetOriginCaller(alice) + title := "Simple Form" + description := "This is a form" + openAt := "2021-01-01T00:00:00Z" + closeAt := "2021-01-02T00:00:00Z" + data := `[{"label":"Name","fieldType":"string","required":true},{"label":"Age","fieldType":"number","required":false},{"label":"Is this a test?","fieldType":"boolean","required":false},{"label":"Favorite Food","fieldType":"['Pizza', 'Schnitzel', 'Burger']","required":true},{"label":"Favorite Foods","fieldType":"{'Pizza', 'Schnitzel', 'Burger'}","required":true}]` + + urequire.NotPanics(t, func() { + id := CreateForm(title, description, openAt, closeAt, data) + + form := GetFormByID(id) + + urequire.True(t, strings.Contains(form, data), "Form JSON was not rebuilt properly") + }) +} + +func TestGetForms(t *testing.T) { + alice := testutils.TestAddress("alice") + std.TestSetOriginCaller(alice) + description := "This is a form" + openAt := "2021-01-01T00:00:00Z" + closeAt := "2021-01-02T00:00:00Z" + + urequire.NotPanics(t, func() { + data1 := `[{"label":"Name","fieldType":"string","required":true}]` + CreateForm("NameForm", description, openAt, closeAt, data1) + data2 := `[{"label":"Age","fieldType":"number","required":false}]` + CreateForm("AgeForm", description, openAt, closeAt, data2) + + forms := GetForms() + + urequire.True(t, strings.Contains(forms, data1) && strings.Contains(forms, data2), "Forms JSON were not rebuilt properly") + }) + +} diff --git a/examples/gno.land/r/agherasie/forms/gno.mod b/examples/gno.land/r/agherasie/forms/gno.mod new file mode 100644 index 00000000000..a9b7f0410e3 --- /dev/null +++ b/examples/gno.land/r/agherasie/forms/gno.mod @@ -0,0 +1 @@ +module gno.land/r/agherasie/forms diff --git a/examples/gno.land/r/demo/atomicswap/atomicswap.gno b/examples/gno.land/r/demo/atomicswap/atomicswap.gno new file mode 100644 index 00000000000..d072df97493 --- /dev/null +++ b/examples/gno.land/r/demo/atomicswap/atomicswap.gno @@ -0,0 +1,175 @@ +// Package atomicswap implements a hash time-locked contract (HTLC) for atomic swaps +// between native coins (ugnot) or GRC20 tokens. +// +// An atomic swap allows two parties to exchange assets in a trustless way, where +// either both transfers happen or neither does. The process works as follows: +// +// 1. Alice wants to swap with Bob. She generates a secret and creates a swap with +// Bob's address and the hash of the secret (hashlock). +// +// 2. Bob can claim the assets by providing the correct secret before the timelock expires. +// The secret proves Bob knows the preimage of the hashlock. +// +// 3. If Bob doesn't claim in time, Alice can refund the assets back to herself. +// +// Example usage for native coins: +// +// // Alice creates a swap with 1000ugnot for Bob +// secret := "mysecret" +// hashlock := hex.EncodeToString(sha256.Sum256([]byte(secret))) +// id, _ := atomicswap.NewCoinSwap(bobAddr, hashlock) // -send 1000ugnot +// +// // Bob claims the swap by providing the secret +// atomicswap.Claim(id, "mysecret") +// +// Example usage for GRC20 tokens: +// +// // Alice approves the swap contract to spend her tokens +// token.Approve(swapAddr, 1000) +// +// // Alice creates a swap with 1000 tokens for Bob +// id, _ := atomicswap.NewGRC20Swap(bobAddr, hashlock, "gno.land/r/demo/token") +// +// // Bob claims the swap by providing the secret +// atomicswap.Claim(id, "mysecret") +// +// If Bob doesn't claim in time (default 1 week), Alice can refund: +// +// atomicswap.Refund(id) +package atomicswap + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/grc20reg" +) + +const defaultTimelockDuration = 7 * 24 * time.Hour // 1w + +var ( + swaps avl.Tree // id -> *Swap + counter int +) + +// NewCoinSwap creates a new atomic swap contract for native coins. +// It uses a default timelock duration. +func NewCoinSwap(recipient std.Address, hashlock string) (int, *Swap) { + timelock := time.Now().Add(defaultTimelockDuration) + return NewCustomCoinSwap(recipient, hashlock, timelock) +} + +// NewGRC20Swap creates a new atomic swap contract for grc20 tokens. +// It uses gno.land/r/demo/grc20reg to lookup for a registered token. +func NewGRC20Swap(recipient std.Address, hashlock string, tokenRegistryKey string) (int, *Swap) { + timelock := time.Now().Add(defaultTimelockDuration) + tokenGetter := grc20reg.MustGet(tokenRegistryKey) + token := tokenGetter() + return NewCustomGRC20Swap(recipient, hashlock, timelock, token) +} + +// NewCoinSwapWithTimelock creates a new atomic swap contract for native coin. +// It allows specifying a custom timelock duration. +// It is not callable with `gnokey maketx call`, but can be imported by another contract or `gnokey maketx run`. +func NewCustomCoinSwap(recipient std.Address, hashlock string, timelock time.Time) (int, *Swap) { + sender := std.PreviousRealm().Address() + sent := std.OriginSend() + require(len(sent) != 0, "at least one coin needs to be sent") + + // Create the swap + sendFn := func(to std.Address) { + banker := std.NewBanker(std.BankerTypeRealmSend) + pkgAddr := std.OriginPkgAddress() + banker.SendCoins(pkgAddr, to, sent) + } + amountStr := sent.String() + swap := newSwap(sender, recipient, hashlock, timelock, amountStr, sendFn) + + counter++ + id := strconv.Itoa(counter) + swaps.Set(id, swap) + return counter, swap +} + +// NewCustomGRC20Swap creates a new atomic swap contract for grc20 tokens. +// It is not callable with `gnokey maketx call`, but can be imported by another contract or `gnokey maketx run`. +func NewCustomGRC20Swap(recipient std.Address, hashlock string, timelock time.Time, token *grc20.Token) (int, *Swap) { + sender := std.PreviousRealm().Address() + curAddr := std.CurrentRealm().Address() + + allowance := token.Allowance(sender, curAddr) + require(allowance > 0, "no allowance") + + userTeller := token.CallerTeller() + err := userTeller.TransferFrom(sender, curAddr, allowance) + require(err == nil, "cannot retrieve tokens from allowance") + + amountStr := ufmt.Sprintf("%d%s", allowance, token.GetSymbol()) + sendFn := func(to std.Address) { + err := userTeller.Transfer(to, allowance) + require(err == nil, "cannot transfer tokens") + } + + swap := newSwap(sender, recipient, hashlock, timelock, amountStr, sendFn) + + counter++ + id := strconv.Itoa(counter) + swaps.Set(id, swap) + + return counter, swap +} + +// Claim loads a registered swap and tries to claim it. +func Claim(id int, secret string) { + swap := mustGet(id) + swap.Claim(secret) +} + +// Refund loads a registered swap and tries to refund it. +func Refund(id int) { + swap := mustGet(id) + swap.Refund() +} + +// Render returns a list of swaps (simplified) for the homepage, and swap details when specifying a swap ID. +func Render(path string) string { + if path == "" { // home + output := "" + size := swaps.Size() + max := 10 + swaps.ReverseIterateByOffset(size-max, max, func(key string, value any) bool { + swap := value.(*Swap) + output += ufmt.Sprintf("- %s: %s -(%s)> %s - %s\n", + key, swap.sender, swap.amountStr, swap.recipient, swap.Status()) + return false + }) + return output + } else { // by id + swap, ok := swaps.Get(path) + if !ok { + return "404" + } + return swap.(*Swap).String() + } +} + +// require checks a condition and panics with a message if the condition is false. +func require(check bool, msg string) { + if !check { + panic(msg) + } +} + +// mustGet retrieves a swap by its id or panics. +func mustGet(id int) *Swap { + key := strconv.Itoa(id) + swap, ok := swaps.Get(key) + if !ok { + panic("unknown swap ID") + } + return swap.(*Swap) +} diff --git a/examples/gno.land/r/demo/atomicswap/atomicswap_test.gno b/examples/gno.land/r/demo/atomicswap/atomicswap_test.gno new file mode 100644 index 00000000000..1e39a7d902d --- /dev/null +++ b/examples/gno.land/r/demo/atomicswap/atomicswap_test.gno @@ -0,0 +1,434 @@ +package atomicswap + +import ( + "crypto/sha256" + "encoding/hex" + "std" + "testing" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/r/demo/tests/test20" +) + +var testRun bool + +func TestNewCustomCoinSwap_Claim(t *testing.T) { + t.Skip("skipping due to bad support for unit-test driven banker") + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender1") + recipient := testutils.TestAddress("recipient1") + amount := std.Coins{{Denom: "ugnot", Amount: 1}} + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(1 * time.Hour) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + std.TestSetOriginSend(amount, nil) + id, swap := NewCustomCoinSwap(recipient, hashlockHex, timelock) + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wgc47h6lta047h6lta047h6l56jtjc +- recipient: g1wfjkx6tsd9jkuap3ta047h6lta047h6lkk20gv +- amount: 1ugnot +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test initial state + uassert.Equal(t, sender, swap.sender, "expected sender to match") + uassert.Equal(t, recipient, swap.recipient, "expected recipient to match") + uassert.Equal(t, swap.amountStr, amount.String(), "expected amount to match") + uassert.Equal(t, hashlockHex, swap.hashlock, "expected hashlock to match") + uassert.True(t, swap.timelock.Equal(timelock), "expected timelock to match") + uassert.False(t, swap.claimed, "expected claimed to be false") + uassert.False(t, swap.refunded, "expected refunded to be false") + + // Test claim + std.TestSetRealm(std.NewUserRealm(recipient)) + uassert.PanicsWithMessage(t, "invalid preimage", func() { swap.Claim("invalid") }) + swap.Claim("secret") + uassert.True(t, swap.claimed, "expected claimed to be true") + + // Test refund (should fail because already claimed) + uassert.PanicsWithMessage(t, "already claimed", swap.Refund) + uassert.PanicsWithMessage(t, "already claimed", func() { swap.Claim("secret") }) + + expected = `- status: claimed +- sender: g1wdjkuer9wgc47h6lta047h6lta047h6l56jtjc +- recipient: g1wfjkx6tsd9jkuap3ta047h6lta047h6lkk20gv +- amount: 1ugnot +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestNewCustomCoinSwap_Refund(t *testing.T) { + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender2") + recipient := testutils.TestAddress("recipient2") + amount := std.Coins{{Denom: "ugnot", Amount: 1}} + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(1 * time.Hour) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + std.TestSetOriginSend(amount, nil) + id, swap := NewCustomCoinSwap(recipient, hashlockHex, timelock) // Create a new swap + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wge97h6lta047h6lta047h6ltfacad +- recipient: g1wfjkx6tsd9jkuapjta047h6lta047h6lducc3v +- amount: 1ugnot +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test Refund + pkgAddr := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + std.TestSetOriginPkgAddress(pkgAddr) + std.TestIssueCoins(pkgAddr, std.Coins{{"ugnot", 100000000}}) + uassert.PanicsWithMessage(t, "timelock not expired", swap.Refund) + swap.timelock = time.Now().Add(-1 * time.Hour) // override timelock + swap.Refund() + uassert.True(t, swap.refunded, "expected refunded to be true") + expected = `- status: refunded +- sender: g1wdjkuer9wge97h6lta047h6lta047h6ltfacad +- recipient: g1wfjkx6tsd9jkuapjta047h6lta047h6lducc3v +- amount: 1ugnot +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-13T22:31:30Z +- remaining: 0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestNewCustomGRC20Swap_Claim(t *testing.T) { + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender3") + recipient := testutils.TestAddress("recipient3") + rlm := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(1 * time.Hour) + + test20.PrivateLedger.Mint(sender, 100_000) + test20.PrivateLedger.Approve(sender, rlm, 70_000) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + id, swap := NewCustomGRC20Swap(recipient, hashlockHex, timelock, test20.Token) + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wge47h6lta047h6lta047h6l5rk38l +- recipient: g1wfjkx6tsd9jkuapnta047h6lta047h6ly6k4pv +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test initial state + uassert.Equal(t, sender, swap.sender, "expected sender to match") + uassert.Equal(t, recipient, swap.recipient, "expected recipient to match") + bal := test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(70_000)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + // uassert.Equal(t, swap.amountStr, amount.String(), "expected amount to match") + uassert.Equal(t, hashlockHex, swap.hashlock, "expected hashlock to match") + uassert.True(t, swap.timelock.Equal(timelock), "expected timelock to match") + uassert.False(t, swap.claimed, "expected claimed to be false") + uassert.False(t, swap.refunded, "expected refunded to be false") + + // Test claim + std.TestSetRealm(std.NewUserRealm(recipient)) + uassert.PanicsWithMessage(t, "invalid preimage", func() { swap.Claim("invalid") }) + swap.Claim("secret") + uassert.True(t, swap.claimed, "expected claimed to be true") + + bal = test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(0)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(70_000)) + + // Test refund (should fail because already claimed) + uassert.PanicsWithMessage(t, "already claimed", swap.Refund) + uassert.PanicsWithMessage(t, "already claimed", func() { swap.Claim("secret") }) + + expected = `- status: claimed +- sender: g1wdjkuer9wge47h6lta047h6lta047h6l5rk38l +- recipient: g1wfjkx6tsd9jkuapnta047h6lta047h6ly6k4pv +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestNewCustomGRC20Swap_Refund(t *testing.T) { + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender5") + recipient := testutils.TestAddress("recipient5") + rlm := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(1 * time.Hour) + + test20.PrivateLedger.Mint(sender, 100_000) + test20.PrivateLedger.Approve(sender, rlm, 70_000) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + id, swap := NewCustomGRC20Swap(recipient, hashlockHex, timelock, test20.Token) + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wg647h6lta047h6lta047h6l5p6k3k +- recipient: g1wfjkx6tsd9jkuap4ta047h6lta047h6lmwmj6v +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-14T00:31:30Z +- remaining: 1h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test initial state + uassert.Equal(t, sender, swap.sender, "expected sender to match") + uassert.Equal(t, recipient, swap.recipient, "expected recipient to match") + bal := test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(70_000)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + // Test Refund + pkgAddr := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + std.TestSetOriginPkgAddress(pkgAddr) + std.TestIssueCoins(pkgAddr, std.Coins{{"ugnot", 100000000}}) + uassert.PanicsWithMessage(t, "timelock not expired", swap.Refund) + swap.timelock = time.Now().Add(-1 * time.Hour) // override timelock + swap.Refund() + uassert.True(t, swap.refunded, "expected refunded to be true") + + bal = test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(100_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(0)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + expected = `- status: refunded +- sender: g1wdjkuer9wg647h6lta047h6lta047h6l5p6k3k +- recipient: g1wfjkx6tsd9jkuap4ta047h6lta047h6lmwmj6v +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-13T22:31:30Z +- remaining: 0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestNewGRC20Swap_Claim(t *testing.T) { + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender4") + recipient := testutils.TestAddress("recipient4") + rlm := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(defaultTimelockDuration) + + test20.PrivateLedger.Mint(sender, 100_000) + test20.PrivateLedger.Approve(sender, rlm, 70_000) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + id, swap := NewGRC20Swap(recipient, hashlockHex, "gno.land/r/demo/tests/test20") + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wg697h6lta047h6lta047h6ltt3lty +- recipient: g1wfjkx6tsd9jkuap5ta047h6lta047h6ljg4l2v +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-20T23:31:30Z +- remaining: 168h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test initial state + uassert.Equal(t, sender, swap.sender, "expected sender to match") + uassert.Equal(t, recipient, swap.recipient, "expected recipient to match") + bal := test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(70_000)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + // uassert.Equal(t, swap.amountStr, amount.String(), "expected amount to match") + uassert.Equal(t, hashlockHex, swap.hashlock, "expected hashlock to match") + uassert.True(t, swap.timelock.Equal(timelock), "expected timelock to match") + uassert.False(t, swap.claimed, "expected claimed to be false") + uassert.False(t, swap.refunded, "expected refunded to be false") + + // Test claim + std.TestSetRealm(std.NewUserRealm(recipient)) + uassert.PanicsWithMessage(t, "invalid preimage", func() { swap.Claim("invalid") }) + swap.Claim("secret") + uassert.True(t, swap.claimed, "expected claimed to be true") + + bal = test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(0)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(70_000)) + + // Test refund (should fail because already claimed) + uassert.PanicsWithMessage(t, "already claimed", swap.Refund) + uassert.PanicsWithMessage(t, "already claimed", func() { swap.Claim("secret") }) + + expected = `- status: claimed +- sender: g1wdjkuer9wg697h6lta047h6lta047h6ltt3lty +- recipient: g1wfjkx6tsd9jkuap5ta047h6lta047h6ljg4l2v +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-20T23:31:30Z +- remaining: 168h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestNewGRC20Swap_Refund(t *testing.T) { + defer resetTestState() + + // Setup + sender := testutils.TestAddress("sender6") + recipient := testutils.TestAddress("recipient6") + rlm := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(defaultTimelockDuration) + + test20.PrivateLedger.Mint(sender, 100_000) + test20.PrivateLedger.Approve(sender, rlm, 70_000) + + // Create a new swap + std.TestSetRealm(std.NewUserRealm(sender)) + id, swap := NewGRC20Swap(recipient, hashlockHex, "gno.land/r/demo/tests/test20") + uassert.Equal(t, 1, id) + + expected := `- status: active +- sender: g1wdjkuer9wgm97h6lta047h6lta047h6ltj497r +- recipient: g1wfjkx6tsd9jkuapkta047h6lta047h6lqyf9rv +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-20T23:31:30Z +- remaining: 168h0m0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) + + // Test initial state + uassert.Equal(t, sender, swap.sender, "expected sender to match") + uassert.Equal(t, recipient, swap.recipient, "expected recipient to match") + bal := test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(30_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(70_000)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + // Test Refund + pkgAddr := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + std.TestSetOriginPkgAddress(pkgAddr) + std.TestIssueCoins(pkgAddr, std.Coins{{"ugnot", 100000000}}) + uassert.PanicsWithMessage(t, "timelock not expired", swap.Refund) + swap.timelock = time.Now().Add(-1 * time.Hour) // override timelock + swap.Refund() + uassert.True(t, swap.refunded, "expected refunded to be true") + + bal = test20.Token.BalanceOf(sender) + uassert.Equal(t, bal, uint64(100_000)) + bal = test20.Token.BalanceOf(rlm) + uassert.Equal(t, bal, uint64(0)) + bal = test20.Token.BalanceOf(recipient) + uassert.Equal(t, bal, uint64(0)) + + expected = `- status: refunded +- sender: g1wdjkuer9wgm97h6lta047h6lta047h6ltj497r +- recipient: g1wfjkx6tsd9jkuapkta047h6lta047h6lqyf9rv +- amount: 70000TST +- hashlock: 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b +- timelock: 2009-02-13T22:31:30Z +- remaining: 0s` + uassert.Equal(t, expected, swap.String()) + uassert.Equal(t, expected, Render("1")) +} + +func TestRender(t *testing.T) { + defer resetTestState() + + // Setup + alice := testutils.TestAddress("alice") + bob := testutils.TestAddress("bob") + charly := testutils.TestAddress("charly") + rlm := std.DerivePkgAddr("gno.land/r/demo/atomicswap") + hashlock := sha256.Sum256([]byte("secret")) + hashlockHex := hex.EncodeToString(hashlock[:]) + timelock := time.Now().Add(1 * time.Hour) + + test20.PrivateLedger.Mint(alice, 100_000) + std.TestSetRealm(std.NewUserRealm(alice)) + + userTeller := test20.Token.CallerTeller() + userTeller.Approve(rlm, 10_000) + _, bobSwap := NewCustomGRC20Swap(bob, hashlockHex, timelock, test20.Token) + + userTeller.Approve(rlm, 20_000) + _, _ = NewCustomGRC20Swap(charly, hashlockHex, timelock, test20.Token) + + std.TestSetRealm(std.NewUserRealm(bob)) + bobSwap.Claim("secret") + + expected := `- 2: g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh -(20000TST)> g1vd5xzunv09047h6lta047h6lta047h6lhsyveh - active +- 1: g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh -(10000TST)> g1vfhkyh6lta047h6lta047h6lta047h6l03vdhu - claimed +` + uassert.Equal(t, expected, Render("")) +} + +func resetTestState() { + swaps = avl.Tree{} + counter = 0 +} diff --git a/examples/gno.land/r/demo/atomicswap/gno.mod b/examples/gno.land/r/demo/atomicswap/gno.mod new file mode 100644 index 00000000000..1d6580c51e8 --- /dev/null +++ b/examples/gno.land/r/demo/atomicswap/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/atomicswap diff --git a/examples/gno.land/r/demo/atomicswap/swap.gno b/examples/gno.land/r/demo/atomicswap/swap.gno new file mode 100644 index 00000000000..299a369db26 --- /dev/null +++ b/examples/gno.land/r/demo/atomicswap/swap.gno @@ -0,0 +1,98 @@ +package atomicswap + +import ( + "crypto/sha256" + "encoding/hex" + "std" + "time" + + "gno.land/p/demo/ufmt" +) + +// Swap represents an atomic swap contract. +type Swap struct { + sender std.Address + recipient std.Address + hashlock string + timelock time.Time + claimed bool + refunded bool + amountStr string + sendFn func(to std.Address) +} + +func newSwap( + sender std.Address, + recipient std.Address, + hashlock string, + timelock time.Time, + amountStr string, + sendFn func(std.Address), +) *Swap { + require(time.Now().Before(timelock), "timelock must be in the future") + require(hashlock != "", "hashlock must not be empty") + return &Swap{ + recipient: recipient, + sender: sender, + hashlock: hashlock, + timelock: timelock, + claimed: false, + refunded: false, + sendFn: sendFn, + amountStr: amountStr, + } +} + +// Claim allows the recipient to claim the funds if they provide the correct preimage. +func (s *Swap) Claim(preimage string) { + require(!s.claimed, "already claimed") + require(!s.refunded, "already refunded") + require(std.PreviousRealm().Address() == s.recipient, "unauthorized") + + hashlock := sha256.Sum256([]byte(preimage)) + hashlockHex := hex.EncodeToString(hashlock[:]) + require(hashlockHex == s.hashlock, "invalid preimage") + + s.claimed = true + s.sendFn(s.recipient) +} + +// Refund allows the sender to refund the funds after the timelock has expired. +func (s *Swap) Refund() { + require(!s.claimed, "already claimed") + require(!s.refunded, "already refunded") + require(std.PreviousRealm().Address() == s.sender, "unauthorized") + require(time.Now().After(s.timelock), "timelock not expired") + + s.refunded = true + s.sendFn(s.sender) +} + +func (s Swap) Status() string { + switch { + case s.refunded: + return "refunded" + case s.claimed: + return "claimed" + case s.TimeRemaining() < 0: + return "expired" + default: + return "active" + } +} + +func (s Swap) TimeRemaining() time.Duration { + remaining := time.Until(s.timelock) + if remaining < 0 { + return 0 + } + return remaining +} + +// String returns the current state of the swap. +func (s Swap) String() string { + return ufmt.Sprintf( + "- status: %s\n- sender: %s\n- recipient: %s\n- amount: %s\n- hashlock: %s\n- timelock: %s\n- remaining: %s", + s.Status(), s.sender, s.recipient, s.amountStr, s.hashlock, s.timelock.Format(time.RFC3339), s.TimeRemaining().String(), + ) +} diff --git a/examples/gno.land/r/demo/banktest/README.md b/examples/gno.land/r/demo/banktest/README.md index 944757f9b12..7c41d5ca640 100644 --- a/examples/gno.land/r/demo/banktest/README.md +++ b/examples/gno.land/r/demo/banktest/README.md @@ -44,7 +44,7 @@ This means that calls to functions defined within this package are encapsulated // Deposit will take the coins (to the realm's pkgaddr) or return them to user. func Deposit(returnDenom string, returnAmount int64) string { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OriginCaller() send := std.Coins{{returnDenom, returnAmount}} ``` @@ -54,7 +54,7 @@ This is the beginning of the definition of the contract function named "Deposit" // record activity act := &activity{ caller: caller, - sent: std.GetOrigSend(), + sent: std.OriginSend(), returned: send, time: time.Now(), } @@ -74,13 +74,13 @@ Updating the "latest" array for viewing at gno.land/r/demo/banktest: (w/ trailin If the user requested the return of coins... ```go - banker := std.GetBanker(std.BankerTypeOrigSend) + banker := std.NewBanker(std.BankerTypeOriginSend) ``` use a std.Banker instance to return any deposited coins to the original sender. ```go - pkgaddr := std.GetOrigPkgAddr() + pkgaddr := std.OriginPkgAddress() // TODO: use std.Coins constructors, this isn't generally safe. banker.SendCoins(pkgaddr, caller, send) return "returned!" @@ -93,8 +93,8 @@ Finally, the results are rendered via an ABCI query call when you visit [/r/demo ```go func Render(path string) string { // get realm coins. - banker := std.GetBanker(std.BankerTypeReadonly) - coins := banker.GetCoins(std.GetOrigPkgAddr()) + banker := std.NewBanker(std.BankerTypeReadonly) + coins := banker.GetCoins(std.OriginPkgAddress()) // render res := "" diff --git a/examples/gno.land/r/demo/banktest/banktest.gno b/examples/gno.land/r/demo/banktest/banktest.gno index 29c479dd025..94ec07b06e3 100644 --- a/examples/gno.land/r/demo/banktest/banktest.gno +++ b/examples/gno.land/r/demo/banktest/banktest.gno @@ -24,12 +24,12 @@ var latest [10]*activity // Deposit will take the coins (to the realm's pkgaddr) or return them to user. func Deposit(returnDenom string, returnAmount int64) string { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OriginCaller() send := std.Coins{{returnDenom, returnAmount}} // record activity act := &activity{ caller: caller, - sent: std.GetOrigSend(), + sent: std.OriginSend(), returned: send, time: time.Now(), } @@ -39,8 +39,8 @@ func Deposit(returnDenom string, returnAmount int64) string { latest[0] = act // return if any. if returnAmount > 0 { - banker := std.GetBanker(std.BankerTypeOrigSend) - pkgaddr := std.GetOrigPkgAddr() + banker := std.NewBanker(std.BankerTypeOriginSend) + pkgaddr := std.OriginPkgAddress() // TODO: use std.Coins constructors, this isn't generally safe. banker.SendCoins(pkgaddr, caller, send) return "returned!" @@ -51,8 +51,8 @@ func Deposit(returnDenom string, returnAmount int64) string { func Render(path string) string { // get realm coins. - banker := std.GetBanker(std.BankerTypeReadonly) - coins := banker.GetCoins(std.GetOrigPkgAddr()) + banker := std.NewBanker(std.BankerTypeReadonly) + coins := banker.GetCoins(std.OriginPkgAddress()) // render res := "" diff --git a/examples/gno.land/r/demo/banktest/z_0_filetest.gno b/examples/gno.land/r/demo/banktest/z_0_filetest.gno index 5a8c8d70a48..066d6bb17d9 100644 --- a/examples/gno.land/r/demo/banktest/z_0_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_0_filetest.gno @@ -17,18 +17,18 @@ func main() { // set up main address and banktest addr. banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") mainaddr := std.DerivePkgAddr("gno.land/r/demo/bank1") - std.TestSetOrigCaller(mainaddr) - std.TestSetOrigPkgAddr(banktestAddr) + std.TestSetOriginCaller(mainaddr) + std.TestSetOriginPkgAddress(banktestAddr) // get and print balance of mainaddr. // with the SEND, + 200 gnot given by the TestContext, main should have 300gnot. - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) mainbal := banker.GetCoins(mainaddr) println("main before:", mainbal) - // simulate a Deposit call. use Send + OrigSend to simulate -send. + // simulate a Deposit call. use Send + OriginSend to simulate -send. banker.SendCoins(mainaddr, banktestAddr, std.Coins{{"ugnot", 100_000_000}}) - std.TestSetOrigSend(std.Coins{{"ugnot", 100_000_000}}, nil) + std.TestSetOriginSend(std.Coins{{"ugnot", 100_000_000}}, nil) res := banktest.Deposit("ugnot", 50_000_000) println("Deposit():", res) diff --git a/examples/gno.land/r/demo/banktest/z_1_filetest.gno b/examples/gno.land/r/demo/banktest/z_1_filetest.gno index 39682d26330..eebe70f251d 100644 --- a/examples/gno.land/r/demo/banktest/z_1_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_1_filetest.gno @@ -15,9 +15,9 @@ func main() { banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") // simulate a Deposit call. - std.TestSetOrigPkgAddr(banktestAddr) + std.TestSetOriginPkgAddress(banktestAddr) std.TestIssueCoins(banktestAddr, std.Coins{{"ugnot", 100000000}}) - std.TestSetOrigSend(std.Coins{{"ugnot", 100000000}}, nil) + std.TestSetOriginSend(std.Coins{{"ugnot", 100000000}}, nil) res := banktest.Deposit("ugnot", 101000000) println(res) } diff --git a/examples/gno.land/r/demo/banktest/z_2_filetest.gno b/examples/gno.land/r/demo/banktest/z_2_filetest.gno index e839f60354a..454763b3a6f 100644 --- a/examples/gno.land/r/demo/banktest/z_2_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_2_filetest.gno @@ -16,16 +16,16 @@ func main() { // print main balance before. mainaddr := std.DerivePkgAddr("gno.land/r/demo/bank1") - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginCaller(mainaddr) - banker := std.GetBanker(std.BankerTypeReadonly) + banker := std.NewBanker(std.BankerTypeReadonly) mainbal := banker.GetCoins(mainaddr) - println("main before:", mainbal) // plus OrigSend equals 300. + println("main before:", mainbal) // plus OriginSend equals 300. // simulate a Deposit call. - std.TestSetOrigPkgAddr(banktestAddr) + std.TestSetOriginPkgAddress(banktestAddr) std.TestIssueCoins(banktestAddr, std.Coins{{"ugnot", 100000000}}) - std.TestSetOrigSend(std.Coins{{"ugnot", 100000000}}, nil) + std.TestSetOriginSend(std.Coins{{"ugnot", 100000000}}, nil) res := banktest.Deposit("ugnot", 55000000) println("Deposit():", res) diff --git a/examples/gno.land/r/demo/banktest/z_3_filetest.gno b/examples/gno.land/r/demo/banktest/z_3_filetest.gno index 7b6758c3e4f..e5796b4de72 100644 --- a/examples/gno.land/r/demo/banktest/z_3_filetest.gno +++ b/examples/gno.land/r/demo/banktest/z_3_filetest.gno @@ -13,12 +13,11 @@ func main() { banktestAddr := std.DerivePkgAddr("gno.land/r/demo/banktest") mainaddr := std.DerivePkgAddr("gno.land/r/demo/bank1") - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginCaller(mainaddr) - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) send := std.Coins{{"ugnot", 123}} banker.SendCoins(banktestAddr, mainaddr, send) - } // Error: diff --git a/examples/gno.land/r/demo/bar20/bar20.gno b/examples/gno.land/r/demo/bar20/bar20.gno index 52f1baa7408..14ef7ada17d 100644 --- a/examples/gno.land/r/demo/bar20/bar20.gno +++ b/examples/gno.land/r/demo/bar20/bar20.gno @@ -22,7 +22,7 @@ func init() { } func Faucet() string { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if err := adm.Mint(caller, 1_000_000); err != nil { return "error: " + err.Error() } diff --git a/examples/gno.land/r/demo/bar20/bar20_test.gno b/examples/gno.land/r/demo/bar20/bar20_test.gno index 0561d13c865..81e986a7584 100644 --- a/examples/gno.land/r/demo/bar20/bar20_test.gno +++ b/examples/gno.land/r/demo/bar20/bar20_test.gno @@ -11,7 +11,7 @@ import ( func TestPackage(t *testing.T) { alice := testutils.TestAddress("alice") std.TestSetRealm(std.NewUserRealm(alice)) - std.TestSetOrigCaller(alice) // XXX: should not need this + std.TestSetOriginCaller(alice) // XXX: should not need this urequire.Equal(t, UserTeller.BalanceOf(alice), uint64(0)) urequire.Equal(t, Faucet(), "OK") diff --git a/examples/gno.land/r/demo/boards/README.md b/examples/gno.land/r/demo/boards/README.md deleted file mode 100644 index 174e1c242fc..00000000000 --- a/examples/gno.land/r/demo/boards/README.md +++ /dev/null @@ -1,147 +0,0 @@ -This is a demo of Gno smart contract programming. This document was -constructed by Gno onto a smart contract hosted on the data Realm -name ["gno.land/r/demo/boards"](https://gno.land/r/demo/boards/) -([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)). - - - -## Build `gnokey`, create your account, and interact with Gno. - -NOTE: Where you see `-remote localhost:26657` here, that flag can be replaced -with `-remote gno.land:26657` if you have $GNOT on the testnet. -(To use the testnet, also replace `-chainid dev` with `-chainid portal-loop` .) - -### Build `gnokey` (and other tools). - -```bash -git clone git@github.com:gnolang/gno.git -cd gno/gno.land -make build -``` - -### Generate a seed/mnemonic code. - -```bash -./build/gnokey generate -``` - -NOTE: You can generate 24 words with any good bip39 generator. - -### Create a new account using your mnemonic. - -```bash -./build/gnokey add -recover KEYNAME -``` - -NOTE: `KEYNAME` is your key identifier, and should be changed. - -### Verify that you can see your account locally. - -```bash -./build/gnokey list -``` - -Take note of your `addr` which looks something like `g17sphqax3kasjptdkmuqvn740u8dhtx4kxl6ljf` . -You will use this as your `ACCOUNT_ADDR`. - -## Interact with the blockchain. - -### Add $GNOT for your account. - -Before starting the `gnoland` node for the first time, your new account can be given $GNOT in the node genesis. -Edit the file `gno.land/genesis/genesis_balances.txt` and add the following line (simlar to the others), using -your `ACCOUNT_ADDR` and `KEYNAME` - -`ACCOUNT_ADDR=10000000000ugnot # @KEYNAME` - -### Alternative: Run a faucet to add $GNOT. - -Instead of editing `gno.land/genesis/genesis_balances.txt`, a more general solution (with more steps) -is to run a local "faucet" and use the web browser to add $GNOT. (This can be done at any time.) -See this page: https://github.com/gnolang/gno/blob/master/contribs/gnofaucet/README.md - - -### Start the `gnoland` node. - -```bash -./build/gnoland start -``` - -NOTE: The node already has the "boards" realm. - -Leave this running in the terminal. In a new terminal, cd to the same folder `gno/gno.land` . - -### Get your current balance, account number, and sequence number. - -```bash -./build/gnokey query auth/accounts/ACCOUNT_ADDR -remote localhost:26657 -``` - -### Register a board username with a smart contract call. - -The `USERNAME` for posting can different than your `KEYNAME`. It is internally linked to your `ACCOUNT_ADDR`. It must be at least 6 characters, lowercase alphanumeric with underscore. - -```bash -./build/gnokey maketx call -pkgpath "gno.land/r/demo/users" -func "Register" -args "" -args "USERNAME" -args "Profile description" -gas-fee "10000000ugnot" -gas-wanted "2000000" -send "200000000ugnot" -broadcast -chainid dev -remote 127.0.0.1:26657 KEYNAME -``` - -Interactive documentation: https://gno.land/r/demo/users$help&func=Register - -### Create a board with a smart contract call. - -```bash -./build/gnokey maketx call -pkgpath "gno.land/r/demo/boards" -func "CreateBoard" -args "BOARDNAME" -gas-fee "1000000ugnot" -gas-wanted "10000000" -broadcast -chainid dev -remote localhost:26657 KEYNAME -``` - -Interactive documentation: https://gno.land/r/demo/boards$help&func=CreateBoard - -Next, query for the permanent board ID by querying (you need this to create a new post): - -```bash -./build/gnokey query "vm/qeval" -data 'gno.land/r/demo/boards.GetBoardIDFromName("BOARDNAME")' -remote localhost:26657 -``` - -### Create a post of a board with a smart contract call. - -NOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased. - -```bash -./build/gnokey maketx call -pkgpath "gno.land/r/demo/boards" -func "CreateThread" -args BOARD_ID -args "Hello gno.land" -args "Text of the post" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME -``` - -Interactive documentation: https://gno.land/r/demo/boards$help&func=CreateThread - -### Create a comment to a post. - -```bash -./build/gnokey maketx call -pkgpath "gno.land/r/demo/boards" -func "CreateReply" -args BOARD_ID -args "1" -args "1" -args "Nice to meet you too." -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid dev -remote localhost:26657 KEYNAME -``` - -Interactive documentation: https://gno.land/r/demo/boards$help&func=CreateReply - -```bash -./build/gnokey query "vm/qrender" -data "gno.land/r/demo/boards:BOARDNAME/1" -remote localhost:26657 -``` - -### Render page with optional path expression. - -The contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling -the `Render(path string)` function like so: - -```bash -./build/gnokey query "vm/qrender" -data "gno.land/r/demo/boards:gnolang" -``` -## View the board in the browser. - -### Start the web server. - -```bash -./build/gnoweb -``` - -This should print something like `Running on http://127.0.0.1:8888` . Leave this running in the terminal. - -### View in the browser - -In your browser, navigate to the printed address http://127.0.0.1:8888 . -To see you post, click on the package `/r/demo/boards` . diff --git a/examples/gno.land/r/demo/boards/board.gno b/examples/gno.land/r/demo/boards/board.gno index 9b9fb730c68..84964f24a00 100644 --- a/examples/gno.land/r/demo/boards/board.gno +++ b/examples/gno.land/r/demo/boards/board.gno @@ -110,7 +110,7 @@ func (board *Board) RenderBoard() string { str := "" str += "\\[[post](" + board.GetPostFormURL() + ")]\n\n" if board.threads.Size() > 0 { - board.threads.Iterate("", "", func(key string, value interface{}) bool { + board.threads.Iterate("", "", func(key string, value any) bool { if str != "" { str += "----------------------------------------\n" } diff --git a/examples/gno.land/r/demo/boards/misc.gno b/examples/gno.land/r/demo/boards/misc.gno index bc561ca7d22..bf87602f058 100644 --- a/examples/gno.land/r/demo/boards/misc.gno +++ b/examples/gno.land/r/demo/boards/misc.gno @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "gno.land/r/demo/users" + "gno.land/r/sys/users" ) //---------------------------------------- @@ -78,18 +78,18 @@ func summaryOf(str string, length int) string { } func displayAddressMD(addr std.Address) string { - user := users.GetUserByAddress(addr) + user := users.ResolveAddress(addr) if user == nil { - return "[" + addr.String() + "](/r/demo/users:" + addr.String() + ")" + return "[" + addr.String() + "](/u/" + addr.String() + ")" } else { - return "[@" + user.Name + "](/r/demo/users:" + user.Name + ")" + return "[@" + user.Name() + "](/u/" + user.Name() + ")" } } func usernameOf(addr std.Address) string { - user := users.GetUserByAddress(addr) + user := users.ResolveAddress(addr) if user == nil { return "" } - return user.Name + return user.Name() } diff --git a/examples/gno.land/r/demo/boards/post.gno b/examples/gno.land/r/demo/boards/post.gno index c6e23cd59d0..b66b7f17912 100644 --- a/examples/gno.land/r/demo/boards/post.gno +++ b/examples/gno.land/r/demo/boards/post.gno @@ -223,7 +223,7 @@ func (post *Post) RenderPost(indent string, levels int) string { str += " \\[[x](" + post.GetDeleteFormURL() + ")]\n" if levels > 0 { if post.replies.Size() > 0 { - post.replies.Iterate("", "", func(key string, value interface{}) bool { + post.replies.Iterate("", "", func(key string, value any) bool { str += indent + "\n" str += value.(*Post).RenderPost(indent+"> ", levels-1) return false diff --git a/examples/gno.land/r/demo/boards/public.gno b/examples/gno.land/r/demo/boards/public.gno index 1d26126fcb2..002370861e6 100644 --- a/examples/gno.land/r/demo/boards/public.gno +++ b/examples/gno.land/r/demo/boards/public.gno @@ -17,11 +17,12 @@ func GetBoardIDFromName(name string) (BoardID, bool) { } func CreateBoard(name string) BoardID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PreviousRealm().IsUser() { panic("invalid non-user call") } + bid := incGetBoardID() - caller := std.GetOrigCaller() + caller := std.OriginCaller() if usernameOf(caller) == "" { panic("unauthorized") } @@ -34,7 +35,7 @@ func CreateBoard(name string) BoardID { } func checkAnonFee() bool { - sent := std.GetOrigSend() + sent := std.OriginSend() anonFeeCoin := std.NewCoin("ugnot", int64(gDefaultAnonFee)) if len(sent) == 1 && sent[0].IsGTE(anonFeeCoin) { return true @@ -43,10 +44,10 @@ func checkAnonFee() bool { } func CreateThread(bid BoardID, title string, body string) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PreviousRealm().IsUser() { panic("invalid non-user call") } - caller := std.GetOrigCaller() + caller := std.OriginCaller() if usernameOf(caller) == "" { if !checkAnonFee() { panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") @@ -61,10 +62,10 @@ func CreateThread(bid BoardID, title string, body string) PostID { } func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PreviousRealm().IsUser() { panic("invalid non-user call") } - caller := std.GetOrigCaller() + caller := std.OriginCaller() if usernameOf(caller) == "" { if !checkAnonFee() { panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") @@ -91,10 +92,10 @@ func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { // If dstBoard is private, does not ping back. // If board specified by bid is private, panics. func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PreviousRealm().IsUser() { panic("invalid non-user call") } - caller := std.GetOrigCaller() + caller := std.OriginCaller() if usernameOf(caller) == "" { // TODO: allow with gDefaultAnonFee payment. if !checkAnonFee() { @@ -121,10 +122,10 @@ func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoar } func DeletePost(bid BoardID, threadid, postid PostID, reason string) { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PreviousRealm().IsUser() { panic("invalid non-user call") } - caller := std.GetOrigCaller() + caller := std.OriginCaller() board := getBoard(bid) if board == nil { panic("board not exist") @@ -153,10 +154,10 @@ func DeletePost(bid BoardID, threadid, postid PostID, reason string) { } func EditPost(bid BoardID, threadid, postid PostID, title, body string) { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { + if !std.PreviousRealm().IsUser() { panic("invalid non-user call") } - caller := std.GetOrigCaller() + caller := std.OriginCaller() board := getBoard(bid) if board == nil { panic("board not exist") diff --git a/examples/gno.land/r/demo/boards/render.gno b/examples/gno.land/r/demo/boards/render.gno index 3709ad02e5d..44a37d30db0 100644 --- a/examples/gno.land/r/demo/boards/render.gno +++ b/examples/gno.land/r/demo/boards/render.gno @@ -19,7 +19,7 @@ func RenderBoard(bid BoardID) string { func Render(path string) string { if path == "" { str := "These are all the boards of this realm:\n\n" - gBoards.Iterate("", "", func(key string, value interface{}) bool { + gBoards.Iterate("", "", func(key string, value any) bool { board := value.(*Board) str += " * [" + board.url + "](" + board.url + ")\n" return false diff --git a/examples/gno.land/r/demo/boards/z_0_a_filetest.gno b/examples/gno.land/r/demo/boards/z_0_a_filetest.gno index 5e8ff520a54..307928c9d22 100644 --- a/examples/gno.land/r/demo/boards/z_0_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_a_filetest.gno @@ -1,13 +1,20 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test +// SEND: 1000000ugnot + import ( + "std" + + "gno.land/p/demo/testutils" "gno.land/r/demo/boards" ) var bid boards.BoardID func init() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) bid = boards.CreateBoard("test_board") boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") pid := boards.CreateThread(bid, "Second Post (title)", "Body of the second post. (body)") diff --git a/examples/gno.land/r/demo/boards/z_0_b_filetest.gno b/examples/gno.land/r/demo/boards/z_0_b_filetest.gno deleted file mode 100644 index 9bcbe9ffafa..00000000000 --- a/examples/gno.land/r/demo/boards/z_0_b_filetest.gno +++ /dev/null @@ -1,23 +0,0 @@ -// PKGPATH: gno.land/r/demo/boards_test -package boards_test - -// SEND: 19900000ugnot - -import ( - "gno.land/r/demo/boards" - "gno.land/r/demo/users" -) - -var bid boards.BoardID - -func init() { - users.Register("", "gnouser", "my profile") - bid = boards.CreateBoard("test_board") -} - -func main() { - println(boards.Render("test_board")) -} - -// Error: -// payment must not be less than 20000000 diff --git a/examples/gno.land/r/demo/boards/z_0_c_filetest.gno b/examples/gno.land/r/demo/boards/z_0_c_filetest.gno index 99fd339aed8..c9b2f9e58ac 100644 --- a/examples/gno.land/r/demo/boards/z_0_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_c_filetest.gno @@ -1,17 +1,20 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var bid boards.BoardID func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") boards.CreateThread(1, "First Post (title)", "Body of the first post. (body)") } diff --git a/examples/gno.land/r/demo/boards/z_0_d_filetest.gno b/examples/gno.land/r/demo/boards/z_0_d_filetest.gno index c77e60e3f3a..ea60d409f10 100644 --- a/examples/gno.land/r/demo/boards/z_0_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_d_filetest.gno @@ -1,17 +1,20 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var bid boards.BoardID func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") boards.CreateReply(bid, 0, 0, "Reply of the second post") } diff --git a/examples/gno.land/r/demo/boards/z_0_e_filetest.gno b/examples/gno.land/r/demo/boards/z_0_e_filetest.gno index 6db036e87ba..be044fd43bf 100644 --- a/examples/gno.land/r/demo/boards/z_0_e_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_e_filetest.gno @@ -1,17 +1,20 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var bid boards.BoardID func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") boards.CreateReply(bid, 0, 0, "Reply of the second post") } diff --git a/examples/gno.land/r/demo/boards/z_0_filetest.gno b/examples/gno.land/r/demo/boards/z_0_filetest.gno index a649895cb01..13ea7226432 100644 --- a/examples/gno.land/r/demo/boards/z_0_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_0_filetest.gno @@ -1,17 +1,20 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 20000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var bid boards.BoardID func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + err := users.Register("gnouser123") bid = boards.CreateBoard("test_board") boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") @@ -30,12 +33,12 @@ func main() { // ## [First Post (title)](/r/demo/boards:test_board/1) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (0 reposts) +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] (0 replies) (0 reposts) // // ---------------------------------------- // ## [Second Post (title)](/r/demo/boards:test_board/2) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] (1 replies) (0 reposts) +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/2) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] (1 replies) (0 reposts) // // diff --git a/examples/gno.land/r/demo/boards/z_10_a_filetest.gno b/examples/gno.land/r/demo/boards/z_10_a_filetest.gno index ad57283bfcf..afc8e2c9e72 100644 --- a/examples/gno.land/r/demo/boards/z_10_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_a_filetest.gno @@ -1,13 +1,13 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot - +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +16,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") pid = boards.CreateThread(bid, "First Post in (title)", "Body of the first post. (body)") @@ -31,4 +32,4 @@ func main() { } // Error: -// board not exist +// invalid non-user call diff --git a/examples/gno.land/r/demo/boards/z_10_b_filetest.gno b/examples/gno.land/r/demo/boards/z_10_b_filetest.gno index cf8a332174f..556e7feaa7d 100644 --- a/examples/gno.land/r/demo/boards/z_10_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_b_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") pid = boards.CreateThread(bid, "First Post in (title)", "Body of the first post. (body)") @@ -31,4 +33,4 @@ func main() { } // Error: -// thread not exist +// invalid non-user call diff --git a/examples/gno.land/r/demo/boards/z_10_c_filetest.gno b/examples/gno.land/r/demo/boards/z_10_c_filetest.gno index 7dd460500d6..2c2dee4ca0e 100644 --- a/examples/gno.land/r/demo/boards/z_10_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_c_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -17,7 +18,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") pid = boards.CreateThread(bid, "First Post in (title)", "Body of the first post. (body)") @@ -26,6 +28,7 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) boards.DeletePost(bid, pid, rid, "") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) @@ -35,15 +38,15 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // > First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=1)] // // ---------------------------------------------------- // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // diff --git a/examples/gno.land/r/demo/boards/z_10_filetest.gno b/examples/gno.land/r/demo/boards/z_10_filetest.gno index 8a6d11c79cf..b6017d91254 100644 --- a/examples/gno.land/r/demo/boards/z_10_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_10_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") pid = boards.CreateThread(bid, "First Post in (title)", "Body of the first post. (body)") @@ -24,6 +26,7 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) boards.DeletePost(bid, pid, pid, "") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) @@ -33,7 +36,7 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // ---------------------------------------------------- // thread does not exist with id: 1 diff --git a/examples/gno.land/r/demo/boards/z_11_a_filetest.gno b/examples/gno.land/r/demo/boards/z_11_a_filetest.gno index d7dc7b90782..9b9cf0b3132 100644 --- a/examples/gno.land/r/demo/boards/z_11_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_a_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") pid = boards.CreateThread(bid, "First Post in (title)", "Body of the first post. (body)") @@ -31,4 +33,4 @@ func main() { } // Error: -// board not exist +// invalid non-user call diff --git a/examples/gno.land/r/demo/boards/z_11_b_filetest.gno b/examples/gno.land/r/demo/boards/z_11_b_filetest.gno index 3aa28095502..e37df330b23 100644 --- a/examples/gno.land/r/demo/boards/z_11_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_b_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") pid = boards.CreateThread(bid, "First Post in (title)", "Body of the first post. (body)") @@ -31,4 +33,4 @@ func main() { } // Error: -// thread not exist +// invalid non-user call diff --git a/examples/gno.land/r/demo/boards/z_11_c_filetest.gno b/examples/gno.land/r/demo/boards/z_11_c_filetest.gno index df764303562..b994badfa4f 100644 --- a/examples/gno.land/r/demo/boards/z_11_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_c_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") pid = boards.CreateThread(bid, "First Post in (title)", "Body of the first post. (body)") @@ -31,4 +33,4 @@ func main() { } // Error: -// post not exist +// invalid non-user call diff --git a/examples/gno.land/r/demo/boards/z_11_d_filetest.gno b/examples/gno.land/r/demo/boards/z_11_d_filetest.gno index f64b4c84bba..fe97a2e6a0f 100644 --- a/examples/gno.land/r/demo/boards/z_11_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_d_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -17,7 +18,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") pid = boards.CreateThread(bid, "First Post in (title)", "Body of the first post. (body)") @@ -26,6 +28,7 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) boards.EditPost(bid, pid, rid, "", "Edited: First reply of the First post\n") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) @@ -35,19 +38,19 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // > First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=1)] // // ---------------------------------------------------- // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // > Edited: First reply of the First post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=1)] // diff --git a/examples/gno.land/r/demo/boards/z_11_filetest.gno b/examples/gno.land/r/demo/boards/z_11_filetest.gno index 3f56293b3bd..214bff56fc1 100644 --- a/examples/gno.land/r/demo/boards/z_11_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_11_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") pid = boards.CreateThread(bid, "First Post in (title)", "Body of the first post. (body)") @@ -24,6 +26,7 @@ func init() { func main() { println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) boards.EditPost(bid, pid, pid, "Edited: First Post in (title)", "Edited: Body of the first post. (body)") println("----------------------------------------------------") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) @@ -33,11 +36,11 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // ---------------------------------------------------- // # Edited: First Post in (title) // // Edited: Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // diff --git a/examples/gno.land/r/demo/boards/z_12_a_filetest.gno b/examples/gno.land/r/demo/boards/z_12_a_filetest.gno index 909be880efa..185634d1eff 100644 --- a/examples/gno.land/r/demo/boards/z_12_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_a_filetest.gno @@ -1,18 +1,19 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( "std" "gno.land/p/demo/testutils" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) func main() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") // create a post via registered user bid1 := boards.CreateBoard("test_board1") pid := boards.CreateThread(bid1, "First Post (title)", "Body of the first post. (body)") @@ -20,8 +21,8 @@ func main() { // create a repost via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) rid := boards.CreateRepost(bid1, pid, "", "Check this out", bid2) println(rid) diff --git a/examples/gno.land/r/demo/boards/z_12_b_filetest.gno b/examples/gno.land/r/demo/boards/z_12_b_filetest.gno index 6b2166895c0..3589712f5aa 100644 --- a/examples/gno.land/r/demo/boards/z_12_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_b_filetest.gno @@ -1,15 +1,18 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) func main() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid1 := boards.CreateBoard("test_board1") pid := boards.CreateThread(bid1, "First Post (title)", "Body of the first post. (body)") bid2 := boards.CreateBoard("test_board2") diff --git a/examples/gno.land/r/demo/boards/z_12_c_filetest.gno b/examples/gno.land/r/demo/boards/z_12_c_filetest.gno index 7397c487d7d..f789c98d732 100644 --- a/examples/gno.land/r/demo/boards/z_12_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_c_filetest.gno @@ -1,15 +1,18 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) func main() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid1 := boards.CreateBoard("test_board1") boards.CreateThread(bid1, "First Post (title)", "Body of the first post. (body)") bid2 := boards.CreateBoard("test_board2") diff --git a/examples/gno.land/r/demo/boards/z_12_d_filetest.gno b/examples/gno.land/r/demo/boards/z_12_d_filetest.gno index 37b6473f7ac..cc997ca8bc1 100644 --- a/examples/gno.land/r/demo/boards/z_12_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_d_filetest.gno @@ -1,15 +1,18 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) func main() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid1 := boards.CreateBoard("test_board1") pid := boards.CreateThread(bid1, "First Post (title)", "Body of the first post. (body)") boards.CreateBoard("test_board2") diff --git a/examples/gno.land/r/demo/boards/z_12_filetest.gno b/examples/gno.land/r/demo/boards/z_12_filetest.gno index ac4adf6ee7b..abe6ed582e0 100644 --- a/examples/gno.land/r/demo/boards/z_12_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_12_filetest.gno @@ -1,11 +1,13 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -15,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid1 = boards.CreateBoard("test_board1") pid = boards.CreateThread(bid1, "First Post (title)", "Body of the first post. (body)") @@ -23,6 +26,7 @@ func init() { } func main() { + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) rid := boards.CreateRepost(bid1, pid, "", "Check this out", bid2) println(rid) println(boards.Render("test_board2")) @@ -37,6 +41,6 @@ func main() { // ## [First Post (title)](/r/demo/boards:test_board1/1) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (1 reposts) +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board1/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] (0 replies) (1 reposts) // // diff --git a/examples/gno.land/r/demo/boards/z_1_filetest.gno b/examples/gno.land/r/demo/boards/z_1_filetest.gno index 4d46c81b83d..816d0df429c 100644 --- a/examples/gno.land/r/demo/boards/z_1_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_1_filetest.gno @@ -1,17 +1,20 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var board *boards.Board func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") _ = boards.CreateBoard("test_board_1") _ = boards.CreateBoard("test_board_2") diff --git a/examples/gno.land/r/demo/boards/z_2_filetest.gno b/examples/gno.land/r/demo/boards/z_2_filetest.gno index 31b39644b24..9d454d525d9 100644 --- a/examples/gno.land/r/demo/boards/z_2_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_2_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") @@ -32,8 +34,8 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // diff --git a/examples/gno.land/r/demo/boards/z_3_filetest.gno b/examples/gno.land/r/demo/boards/z_3_filetest.gno index 0b2a2df2f91..5caf821cacd 100644 --- a/examples/gno.land/r/demo/boards/z_3_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_3_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") @@ -24,6 +26,7 @@ func init() { } func main() { + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) rid := boards.CreateReply(bid, pid, pid, "Reply of the second post") println(rid) println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) @@ -34,8 +37,8 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // 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 b781e94e4db..8cbe25b390c 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") @@ -26,6 +28,7 @@ func init() { } func main() { + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) rid2 := boards.CreateReply(bid, pid, pid, "Second reply of the second post") println(rid2) println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) @@ -37,17 +40,18 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // // > Second reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=4&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=4&threadid=2)] // // Realm: -// switchrealm["gno.land/r/demo/users"] +// switchrealm["gno.land/r/sys/users"] +// switchrealm["gno.land/r/sys/users"] // switchrealm["gno.land/r/demo/boards"] // u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={ // "ObjectInfo": { @@ -905,8 +909,14 @@ func main() { // } // } // switchrealm["gno.land/r/demo/boards"] -// switchrealm["gno.land/r/demo/users"] -// switchrealm["gno.land/r/demo/users"] -// switchrealm["gno.land/r/demo/users"] +// switchrealm["gno.land/r/sys/users"] +// switchrealm["gno.land/r/sys/users"] +// switchrealm["gno.land/r/sys/users"] +// switchrealm["gno.land/r/sys/users"] +// switchrealm["gno.land/r/sys/users"] +// switchrealm["gno.land/r/sys/users"] +// switchrealm["gno.land/r/sys/users"] +// switchrealm["gno.land/r/sys/users"] +// switchrealm["gno.land/r/sys/users"] // switchrealm["gno.land/r/demo/boards"] // switchrealm["gno.land/r/demo/boards_test"] diff --git a/examples/gno.land/r/demo/boards/z_5_b_filetest.gno b/examples/gno.land/r/demo/boards/z_5_b_filetest.gno index e79da5c3677..29b5ddcaee2 100644 --- a/examples/gno.land/r/demo/boards/z_5_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_b_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( "std" @@ -8,20 +8,21 @@ import ( "gno.land/p/demo/testutils" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") func main() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") // create board via registered user bid := boards.CreateBoard("test_board") // create post via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) pid := boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno index 723e6a10204..87a95e250b0 100644 --- a/examples/gno.land/r/demo/boards/z_5_c_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_c_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( "std" @@ -8,20 +8,21 @@ import ( "gno.land/p/demo/testutils" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") func main() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") // create board via registered user bid := boards.CreateBoard("test_board") // create post via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 101000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 101000000}}, nil) pid := boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") boards.CreateReply(bid, pid, pid, "Reply of the first post") @@ -33,8 +34,8 @@ func main() { // # First Post (title) // // Body of the first post. (body) -// \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/u/g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=1&threadid=1)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] // // > Reply of the first post -// > \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/demo/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/u/g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/1/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=1)] // diff --git a/examples/gno.land/r/demo/boards/z_5_d_filetest.gno b/examples/gno.land/r/demo/boards/z_5_d_filetest.gno index 54cfe49eec6..93d046449c0 100644 --- a/examples/gno.land/r/demo/boards/z_5_d_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_d_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( "std" @@ -8,21 +8,22 @@ import ( "gno.land/p/demo/testutils" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) const admin = std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") func main() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") // create board via registered user bid := boards.CreateBoard("test_board") pid := boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") // create reply via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) boards.CreateReply(bid, pid, pid, "Reply of the first post") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) diff --git a/examples/gno.land/r/demo/boards/z_5_filetest.gno b/examples/gno.land/r/demo/boards/z_5_filetest.gno index 712af483891..ed9e184c8e7 100644 --- a/examples/gno.land/r/demo/boards/z_5_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_5_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -16,7 +17,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") @@ -25,6 +27,7 @@ func init() { } func main() { + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) rid2 := boards.CreateReply(bid, pid, pid, "Second reply of the second post\n") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) } @@ -33,12 +36,12 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // // > Second reply of the second post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=4&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=4&threadid=2)] // diff --git a/examples/gno.land/r/demo/boards/z_6_filetest.gno b/examples/gno.land/r/demo/boards/z_6_filetest.gno index ec40cf5f8e9..8aeadb0a41d 100644 --- a/examples/gno.land/r/demo/boards/z_6_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_6_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -17,7 +18,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") @@ -26,6 +28,7 @@ func init() { } func main() { + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) boards.CreateReply(bid, pid, pid, "Second reply of the second post\n") boards.CreateReply(bid, pid, rid, "First reply of the first reply\n") println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) @@ -35,16 +38,16 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=2&threadid=2)] \[[repost](/r/demo/boards$help&func=CreateRepost&bid=1&postid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=2&threadid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // > // > > First reply of the first reply // > > -// > > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=5)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=5)] +// > > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=5&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=5&threadid=2)] // // > Second reply of the second post // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=4)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=4&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=4&threadid=2)] // diff --git a/examples/gno.land/r/demo/boards/z_7_filetest.gno b/examples/gno.land/r/demo/boards/z_7_filetest.gno index 353b84f6d87..6126c5602fa 100644 --- a/examples/gno.land/r/demo/boards/z_7_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_7_filetest.gno @@ -1,16 +1,19 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) func init() { // register - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") // create board and post bid := boards.CreateBoard("test_board") @@ -28,6 +31,6 @@ func main() { // ## [First Post (title)](/r/demo/boards:test_board/1) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) (0 reposts) +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm UTC](/r/demo/boards:test_board/1) \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=1&threadid=1)] (0 replies) (0 reposts) // // diff --git a/examples/gno.land/r/demo/boards/z_8_filetest.gno b/examples/gno.land/r/demo/boards/z_8_filetest.gno index 4896dfcfccf..e80e32f2640 100644 --- a/examples/gno.land/r/demo/boards/z_8_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_8_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -17,7 +18,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") bid = boards.CreateBoard("test_board") boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") @@ -26,6 +28,7 @@ func init() { } func main() { + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) boards.CreateReply(bid, pid, pid, "Second reply of the second post\n") rid2 := boards.CreateReply(bid, pid, rid, "First reply of the first reply\n") println(boards.Render("test_board/" + strconv.Itoa(int(pid)) + "/" + strconv.Itoa(int(rid2)))) @@ -35,11 +38,11 @@ func main() { // _[see thread](/r/demo/boards:test_board/2)_ // // Reply of the second post -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=3)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=3)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=3&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=3&threadid=2)] // // _[see all 1 replies](/r/demo/boards:test_board/2/3)_ // // > First reply of the first reply // > -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&threadid=2&postid=5)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&threadid=2&postid=5)] +// > \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/5) \[[reply](/r/demo/boards$help&func=CreateReply&bid=1&postid=5&threadid=2)] \[[x](/r/demo/boards$help&func=DeletePost&bid=1&postid=5&threadid=2)] // diff --git a/examples/gno.land/r/demo/boards/z_9_a_filetest.gno b/examples/gno.land/r/demo/boards/z_9_a_filetest.gno index 8d07ba0e710..d9cd7a94497 100644 --- a/examples/gno.land/r/demo/boards/z_9_a_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_9_a_filetest.gno @@ -1,17 +1,20 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var dstBoard boards.BoardID func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") dstBoard = boards.CreateBoard("dst_board") diff --git a/examples/gno.land/r/demo/boards/z_9_b_filetest.gno b/examples/gno.land/r/demo/boards/z_9_b_filetest.gno index 68daf770b4f..4d889e1c7cc 100644 --- a/examples/gno.land/r/demo/boards/z_9_b_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_9_b_filetest.gno @@ -1,11 +1,13 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -14,7 +16,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") srcBoard = boards.CreateBoard("first_board") pid = boards.CreateThread(srcBoard, "First Post in (title)", "Body of the first post. (body)") diff --git a/examples/gno.land/r/demo/boards/z_9_filetest.gno b/examples/gno.land/r/demo/boards/z_9_filetest.gno index ca37e306bda..5a9a1c967b4 100644 --- a/examples/gno.land/r/demo/boards/z_9_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_9_filetest.gno @@ -1,13 +1,14 @@ // PKGPATH: gno.land/r/demo/boards_test package boards_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" "strconv" "gno.land/r/demo/boards" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var ( @@ -17,7 +18,8 @@ var ( ) func init() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") firstBoard = boards.CreateBoard("first_board") secondBoard = boards.CreateBoard("second_board") @@ -34,5 +36,5 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=2&threadid=1&postid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=2&threadid=1&postid=1)] +// \- [@gnouser123](/u/gnouser123), [2009-02-13 11:31pm (UTC)](/r/demo/boards:second_board/1/1) \[[reply](/r/demo/boards$help&func=CreateReply&bid=2&postid=1&threadid=1)] \[[x](/r/demo/boards$help&func=DeletePost&bid=2&postid=1&threadid=1)] // diff --git a/examples/gno.land/r/demo/btree_dao/btree_dao.gno b/examples/gno.land/r/demo/btree_dao/btree_dao.gno index c90742eb29b..66b857580c1 100644 --- a/examples/gno.land/r/demo/btree_dao/btree_dao.gno +++ b/examples/gno.land/r/demo/btree_dao/btree_dao.gno @@ -57,7 +57,7 @@ func PlantSeed(message string) error { // Returns an error if any validation fails or if NFT minting fails. func plantImpl(userBTree *btree.BTree, seedMessage string) error { // Get the caller's address - userAddress := std.GetOrigCaller() + userAddress := std.OriginCaller() var nftID string var regDetails *RegistrationDetails diff --git a/examples/gno.land/r/demo/btree_dao/btree_dao_test.gno b/examples/gno.land/r/demo/btree_dao/btree_dao_test.gno index 0514f52f7b4..e6ad0c3f732 100644 --- a/examples/gno.land/r/demo/btree_dao/btree_dao_test.gno +++ b/examples/gno.land/r/demo/btree_dao/btree_dao_test.gno @@ -12,7 +12,7 @@ import ( ) func setupTest() { - std.TestSetOrigCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) + std.TestSetOriginCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) members = btree.New() } diff --git a/examples/gno.land/r/demo/disperse/disperse.gno b/examples/gno.land/r/demo/disperse/disperse.gno index 0dc833dda95..41319992905 100644 --- a/examples/gno.land/r/demo/disperse/disperse.gno +++ b/examples/gno.land/r/demo/disperse/disperse.gno @@ -7,15 +7,15 @@ import ( ) // Get address of Disperse realm -var realmAddr = std.CurrentRealm().Addr() +var realmAddr = std.CurrentRealm().Address() // DisperseUgnot parses receivers and amounts and sends out ugnot // The function will send out the coins to the addresses and return the leftover coins to the caller // if there are any to return func DisperseUgnot(addresses []std.Address, coins std.Coins) { - coinSent := std.GetOrigSend() - caller := std.PrevRealm().Addr() - banker := std.GetBanker(std.BankerTypeOrigSend) + coinSent := std.OriginSend() + caller := std.PreviousRealm().Address() + banker := std.NewBanker(std.BankerTypeOriginSend) if len(addresses) != len(coins) { panic(ErrNumAddrValMismatch) @@ -50,7 +50,7 @@ func DisperseUgnot(addresses []std.Address, coins std.Coins) { // Note that it is necessary to approve the realm to spend the tokens before calling this function // see the corresponding filetests for examples func DisperseGRC20(addresses []std.Address, amounts []uint64, symbols []string) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if (len(addresses) != len(amounts)) || (len(amounts) != len(symbols)) { panic(ErrArgLenAndSentLenMismatch) diff --git a/examples/gno.land/r/demo/disperse/z_0_filetest.gno b/examples/gno.land/r/demo/disperse/z_0_filetest.gno index ca1e9ea0ce8..695a7f7c2b8 100644 --- a/examples/gno.land/r/demo/disperse/z_0_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_0_filetest.gno @@ -14,10 +14,10 @@ func main() { disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") - std.TestSetOrigPkgAddr(disperseAddr) - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginPkgAddress(disperseAddr) + std.TestSetOriginCaller(mainaddr) - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) mainbal := banker.GetCoins(mainaddr) println("main before:", mainbal) diff --git a/examples/gno.land/r/demo/disperse/z_1_filetest.gno b/examples/gno.land/r/demo/disperse/z_1_filetest.gno index 4c27c50749f..a4c0d19b15a 100644 --- a/examples/gno.land/r/demo/disperse/z_1_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_1_filetest.gno @@ -14,10 +14,10 @@ func main() { disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") - std.TestSetOrigPkgAddr(disperseAddr) - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginPkgAddress(disperseAddr) + std.TestSetOriginCaller(mainaddr) - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) mainbal := banker.GetCoins(mainaddr) println("main before:", mainbal) diff --git a/examples/gno.land/r/demo/disperse/z_2_filetest.gno b/examples/gno.land/r/demo/disperse/z_2_filetest.gno index 79e8d81e2b1..0e6d9a5cd1a 100644 --- a/examples/gno.land/r/demo/disperse/z_2_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_2_filetest.gno @@ -14,10 +14,10 @@ func main() { disperseAddr := std.DerivePkgAddr("gno.land/r/demo/disperse") mainaddr := std.DerivePkgAddr("gno.land/r/demo/main") - std.TestSetOrigPkgAddr(disperseAddr) - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginPkgAddress(disperseAddr) + std.TestSetOriginCaller(mainaddr) - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) banker.SendCoins(mainaddr, disperseAddr, std.Coins{{"ugnot", 100}}) disperse.DisperseUgnotString("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0,g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c", "150,50") diff --git a/examples/gno.land/r/demo/disperse/z_3_filetest.gno b/examples/gno.land/r/demo/disperse/z_3_filetest.gno index 7cb7ffbe71d..4c9ab05728a 100644 --- a/examples/gno.land/r/demo/disperse/z_3_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_3_filetest.gno @@ -17,10 +17,10 @@ func main() { beneficiary1 := std.Address("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0") beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c") - std.TestSetOrigPkgAddr(disperseAddr) - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginPkgAddress(disperseAddr) + std.TestSetOriginCaller(mainaddr) - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) tokens.New("test", "TEST", 4, 0, 0) tokens.Mint("TEST", mainaddr, 200) diff --git a/examples/gno.land/r/demo/disperse/z_4_filetest.gno b/examples/gno.land/r/demo/disperse/z_4_filetest.gno index 4dafb780e83..2cfc9c820d1 100644 --- a/examples/gno.land/r/demo/disperse/z_4_filetest.gno +++ b/examples/gno.land/r/demo/disperse/z_4_filetest.gno @@ -17,10 +17,10 @@ func main() { beneficiary1 := std.Address("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0") beneficiary2 := std.Address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c") - std.TestSetOrigPkgAddr(disperseAddr) - std.TestSetOrigCaller(mainaddr) + std.TestSetOriginPkgAddress(disperseAddr) + std.TestSetOriginCaller(mainaddr) - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) tokens.New("test1", "TEST1", 4, 0, 0) tokens.Mint("TEST1", mainaddr, 200) diff --git a/examples/gno.land/r/demo/foo1155/foo1155.gno b/examples/gno.land/r/demo/foo1155/foo1155.gno index 2bd3b7a84c0..feb6e4bf0b0 100644 --- a/examples/gno.land/r/demo/foo1155/foo1155.gno +++ b/examples/gno.land/r/demo/foo1155/foo1155.gno @@ -5,9 +5,6 @@ import ( "gno.land/p/demo/grc/grc1155" "gno.land/p/demo/ufmt" - "gno.land/r/demo/users" - - pusers "gno.land/p/demo/users" ) var ( @@ -29,8 +26,8 @@ func mintGRC1155Token(owner std.Address) { // Getters -func BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 { - balance, err := foo.BalanceOf(users.Resolve(user), tid) +func BalanceOf(user std.Address, tid grc1155.TokenID) uint64 { + balance, err := foo.BalanceOf(user, tid) if err != nil { panic(err) } @@ -38,13 +35,8 @@ func BalanceOf(user pusers.AddressOrName, tid grc1155.TokenID) uint64 { return balance } -func BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 { - var usersResolved []std.Address - - for i := 0; i < len(ul); i++ { - usersResolved[i] = users.Resolve(ul[i]) - } - balanceBatch, err := foo.BalanceOfBatch(usersResolved, batch) +func BalanceOfBatch(ul []std.Address, batch []grc1155.TokenID) []uint64 { + balanceBatch, err := foo.BalanceOfBatch(ul, batch) if err != nil { panic(err) } @@ -52,28 +44,28 @@ func BalanceOfBatch(ul []pusers.AddressOrName, batch []grc1155.TokenID) []uint64 return balanceBatch } -func IsApprovedForAll(owner, user pusers.AddressOrName) bool { - return foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user)) +func IsApprovedForAll(owner, user std.Address) bool { + return foo.IsApprovedForAll(owner, user) } // Setters -func SetApprovalForAll(user pusers.AddressOrName, approved bool) { - err := foo.SetApprovalForAll(users.Resolve(user), approved) +func SetApprovalForAll(user std.Address, approved bool) { + err := foo.SetApprovalForAll(user, approved) if err != nil { panic(err) } } -func TransferFrom(from, to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { - err := foo.SafeTransferFrom(users.Resolve(from), users.Resolve(to), tid, amount) +func TransferFrom(from, to std.Address, tid grc1155.TokenID, amount uint64) { + err := foo.SafeTransferFrom(from, to, tid, amount) if err != nil { panic(err) } } -func BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) { - err := foo.SafeBatchTransferFrom(users.Resolve(from), users.Resolve(to), batch, amounts) +func BatchTransferFrom(from, to std.Address, batch []grc1155.TokenID, amounts []uint64) { + err := foo.SafeBatchTransferFrom(from, to, batch, amounts) if err != nil { panic(err) } @@ -81,37 +73,37 @@ func BatchTransferFrom(from, to pusers.AddressOrName, batch []grc1155.TokenID, a // Admin -func Mint(to pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { - caller := std.GetOrigCaller() +func Mint(to std.Address, tid grc1155.TokenID, amount uint64) { + caller := std.OriginCaller() assertIsAdmin(caller) - err := foo.SafeMint(users.Resolve(to), tid, amount) + err := foo.SafeMint(to, tid, amount) if err != nil { panic(err) } } -func MintBatch(to pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) { - caller := std.GetOrigCaller() +func MintBatch(to std.Address, batch []grc1155.TokenID, amounts []uint64) { + caller := std.OriginCaller() assertIsAdmin(caller) - err := foo.SafeBatchMint(users.Resolve(to), batch, amounts) + err := foo.SafeBatchMint(to, batch, amounts) if err != nil { panic(err) } } -func Burn(from pusers.AddressOrName, tid grc1155.TokenID, amount uint64) { - caller := std.GetOrigCaller() +func Burn(from std.Address, tid grc1155.TokenID, amount uint64) { + caller := std.OriginCaller() assertIsAdmin(caller) - err := foo.Burn(users.Resolve(from), tid, amount) + err := foo.Burn(from, tid, amount) if err != nil { panic(err) } } -func BurnBatch(from pusers.AddressOrName, batch []grc1155.TokenID, amounts []uint64) { - caller := std.GetOrigCaller() +func BurnBatch(from std.Address, batch []grc1155.TokenID, amounts []uint64) { + caller := std.OriginCaller() assertIsAdmin(caller) - err := foo.BatchBurn(users.Resolve(from), batch, amounts) + err := foo.BatchBurn(from, batch, amounts) if err != nil { panic(err) } diff --git a/examples/gno.land/r/demo/foo1155/foo1155_test.gno b/examples/gno.land/r/demo/foo1155/foo1155_test.gno index 64d4bc1256f..dd4aad49a18 100644 --- a/examples/gno.land/r/demo/foo1155/foo1155_test.gno +++ b/examples/gno.land/r/demo/foo1155/foo1155_test.gno @@ -1,26 +1,25 @@ package foo1155 import ( + "std" "testing" "gno.land/p/demo/grc/grc1155" - "gno.land/p/demo/users" ) func TestFoo721(t *testing.T) { - admin := users.AddressOrName("g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530") - bob := users.AddressOrName("g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw") + admin := std.Address("g10x5phu0k6p64cwrhfpsc8tk43st9kug6wft530") + bob := std.Address("g1ze6et22ces5atv79y4xh38s4kuraey4y2fr6tw") tid1 := grc1155.TokenID("1") - tid2 := grc1155.TokenID("2") - for i, tc := range []struct { + for _, tc := range []struct { name string - expected interface{} - fn func() interface{} + expected any + fn func() any }{ - {"BalanceOf(admin, tid1)", uint64(100), func() interface{} { return BalanceOf(admin, tid1) }}, - {"BalanceOf(bob, tid1)", uint64(0), func() interface{} { return BalanceOf(bob, tid1) }}, - {"IsApprovedForAll(admin, bob)", false, func() interface{} { return IsApprovedForAll(admin, bob) }}, + {"BalanceOf(admin, tid1)", uint64(100), func() any { return BalanceOf(admin, tid1) }}, + {"BalanceOf(bob, tid1)", uint64(0), func() any { return BalanceOf(bob, tid1) }}, + {"IsApprovedForAll(admin, bob)", false, func() any { return IsApprovedForAll(admin, bob) }}, } { t.Run(tc.name, func(t *testing.T) { got := tc.fn() diff --git a/examples/gno.land/r/demo/foo20/foo20.gno b/examples/gno.land/r/demo/foo20/foo20.gno index 6522fbdc90e..d79cf3b3dc4 100644 --- a/examples/gno.land/r/demo/foo20/foo20.gno +++ b/examples/gno.land/r/demo/foo20/foo20.gno @@ -9,9 +9,7 @@ import ( "gno.land/p/demo/grc/grc20" "gno.land/p/demo/ownable" "gno.land/p/demo/ufmt" - pusers "gno.land/p/demo/users" "gno.land/r/demo/grc20reg" - "gno.land/r/demo/users" ) var ( @@ -29,51 +27,42 @@ func TotalSupply() uint64 { return UserTeller.TotalSupply() } -func BalanceOf(owner pusers.AddressOrName) uint64 { - ownerAddr := users.Resolve(owner) - return UserTeller.BalanceOf(ownerAddr) +func BalanceOf(owner std.Address) uint64 { + return UserTeller.BalanceOf(owner) } -func Allowance(owner, spender pusers.AddressOrName) uint64 { - ownerAddr := users.Resolve(owner) - spenderAddr := users.Resolve(spender) - return UserTeller.Allowance(ownerAddr, spenderAddr) +func Allowance(owner, spender std.Address) uint64 { + return UserTeller.Allowance(owner, spender) } -func Transfer(to pusers.AddressOrName, amount uint64) { - toAddr := users.Resolve(to) - checkErr(UserTeller.Transfer(toAddr, amount)) +func Transfer(to std.Address, amount uint64) { + checkErr(UserTeller.Transfer(to, amount)) } -func Approve(spender pusers.AddressOrName, amount uint64) { - spenderAddr := users.Resolve(spender) - checkErr(UserTeller.Approve(spenderAddr, amount)) +func Approve(spender std.Address, amount uint64) { + checkErr(UserTeller.Approve(spender, amount)) } -func TransferFrom(from, to pusers.AddressOrName, amount uint64) { - fromAddr := users.Resolve(from) - toAddr := users.Resolve(to) - checkErr(UserTeller.TransferFrom(fromAddr, toAddr, amount)) +func TransferFrom(from, to std.Address, amount uint64) { + checkErr(UserTeller.TransferFrom(from, to, amount)) } // Faucet is distributing foo20 tokens without restriction (unsafe). // For a real token faucet, you should take care of setting limits are asking payment. func Faucet() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() amount := uint64(1_000 * 10_000) // 1k checkErr(privateLedger.Mint(caller, amount)) } -func Mint(to pusers.AddressOrName, amount uint64) { +func Mint(to std.Address, amount uint64) { Ownable.AssertCallerIsOwner() - toAddr := users.Resolve(to) - checkErr(privateLedger.Mint(toAddr, amount)) + checkErr(privateLedger.Mint(to, amount)) } -func Burn(from pusers.AddressOrName, amount uint64) { +func Burn(from std.Address, amount uint64) { Ownable.AssertCallerIsOwner() - fromAddr := users.Resolve(from) - checkErr(privateLedger.Burn(fromAddr, amount)) + checkErr(privateLedger.Burn(from, amount)) } func Render(path string) string { @@ -84,9 +73,8 @@ func Render(path string) string { case path == "": return Token.RenderHome() case c == 2 && parts[0] == "balance": - owner := pusers.AddressOrName(parts[1]) - ownerAddr := users.Resolve(owner) - balance := UserTeller.BalanceOf(ownerAddr) + owner := std.Address(parts[1]) + balance := UserTeller.BalanceOf(owner) return ufmt.Sprintf("%d\n", balance) default: return "404\n" diff --git a/examples/gno.land/r/demo/foo20/foo20_test.gno b/examples/gno.land/r/demo/foo20/foo20_test.gno index b9e80fbb476..8773114a681 100644 --- a/examples/gno.land/r/demo/foo20/foo20_test.gno +++ b/examples/gno.land/r/demo/foo20/foo20_test.gno @@ -6,15 +6,13 @@ import ( "gno.land/p/demo/testutils" "gno.land/p/demo/uassert" - pusers "gno.land/p/demo/users" - "gno.land/r/demo/users" ) func TestReadOnlyPublicMethods(t *testing.T) { var ( - admin = pusers.AddressOrName("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - alice = pusers.AddressOrName(testutils.TestAddress("alice")) - bob = pusers.AddressOrName(testutils.TestAddress("bob")) + admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") ) type test struct { @@ -39,7 +37,7 @@ func TestReadOnlyPublicMethods(t *testing.T) { } // bob uses the faucet. - std.TestSetOrigCaller(users.Resolve(bob)) + std.TestSetOriginCaller(bob) Faucet() // check balances #2. @@ -60,9 +58,8 @@ func TestReadOnlyPublicMethods(t *testing.T) { func TestErrConditions(t *testing.T) { var ( - admin = pusers.AddressOrName("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - alice = pusers.AddressOrName(testutils.TestAddress("alice")) - empty = pusers.AddressOrName("") + admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + empty = std.Address("") ) type test struct { diff --git a/examples/gno.land/r/demo/foo721/foo721.gno b/examples/gno.land/r/demo/foo721/foo721.gno index f7364d4185f..bab4664c6e4 100644 --- a/examples/gno.land/r/demo/foo721/foo721.gno +++ b/examples/gno.land/r/demo/foo721/foo721.gno @@ -5,9 +5,6 @@ import ( "gno.land/p/demo/grc/grc721" "gno.land/p/demo/ufmt" - "gno.land/r/demo/users" - - pusers "gno.land/p/demo/users" ) var ( @@ -30,8 +27,8 @@ func mintNNFT(owner std.Address, n uint64) { // Getters -func BalanceOf(user pusers.AddressOrName) uint64 { - balance, err := foo.BalanceOf(users.Resolve(user)) +func BalanceOf(user std.Address) uint64 { + balance, err := foo.BalanceOf(user) if err != nil { panic(err) } @@ -48,8 +45,8 @@ func OwnerOf(tid grc721.TokenID) std.Address { return owner } -func IsApprovedForAll(owner, user pusers.AddressOrName) bool { - return foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user)) +func IsApprovedForAll(owner, user std.Address) bool { + return foo.IsApprovedForAll(owner, user) } func GetApproved(tid grc721.TokenID) std.Address { @@ -63,22 +60,22 @@ func GetApproved(tid grc721.TokenID) std.Address { // Setters -func Approve(user pusers.AddressOrName, tid grc721.TokenID) { - err := foo.Approve(users.Resolve(user), tid) +func Approve(user std.Address, tid grc721.TokenID) { + err := foo.Approve(user, tid) if err != nil { panic(err) } } -func SetApprovalForAll(user pusers.AddressOrName, approved bool) { - err := foo.SetApprovalForAll(users.Resolve(user), approved) +func SetApprovalForAll(user std.Address, approved bool) { + err := foo.SetApprovalForAll(user, approved) if err != nil { panic(err) } } -func TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) { - err := foo.TransferFrom(users.Resolve(from), users.Resolve(to), tid) +func TransferFrom(from, to std.Address, tid grc721.TokenID) { + err := foo.TransferFrom(from, to, tid) if err != nil { panic(err) } @@ -86,17 +83,17 @@ func TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) { // Admin -func Mint(to pusers.AddressOrName, tid grc721.TokenID) { - caller := std.PrevRealm().Addr() +func Mint(to std.Address, tid grc721.TokenID) { + caller := std.PreviousRealm().Address() assertIsAdmin(caller) - err := foo.Mint(users.Resolve(to), tid) + err := foo.Mint(to, tid) if err != nil { panic(err) } } func Burn(tid grc721.TokenID) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() assertIsAdmin(caller) err := foo.Burn(tid) if err != nil { diff --git a/examples/gno.land/r/demo/foo721/foo721_test.gno b/examples/gno.land/r/demo/foo721/foo721_test.gno index 2477ca34fde..0a10dd51036 100644 --- a/examples/gno.land/r/demo/foo721/foo721_test.gno +++ b/examples/gno.land/r/demo/foo721/foo721_test.gno @@ -1,26 +1,24 @@ package foo721 import ( + "std" "testing" "gno.land/p/demo/grc/grc721" - "gno.land/r/demo/users" - - pusers "gno.land/p/demo/users" ) func TestFoo721(t *testing.T) { - admin := pusers.AddressOrName("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - hariom := pusers.AddressOrName("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + admin := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + hariom := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - for i, tc := range []struct { + for _, tc := range []struct { name string - expected interface{} - fn func() interface{} + expected any + fn func() any }{ {"BalanceOf(admin)", uint64(10), func() interface{} { return BalanceOf(admin) }}, {"BalanceOf(hariom)", uint64(5), func() interface{} { return BalanceOf(hariom) }}, - {"OwnerOf(0)", users.Resolve(admin), func() interface{} { return OwnerOf(grc721.TokenID("0")) }}, + {"OwnerOf(0)", admin, func() interface{} { return OwnerOf(grc721.TokenID("0")) }}, {"IsApprovedForAll(admin, hariom)", false, func() interface{} { return IsApprovedForAll(admin, hariom) }}, } { t.Run(tc.name, func(t *testing.T) { diff --git a/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno b/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno index 4dbbd6c7682..e8db2ab0651 100644 --- a/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno +++ b/examples/gno.land/r/demo/games/dice_roller/dice_roller.gno @@ -12,7 +12,8 @@ import ( "gno.land/p/demo/entropy" "gno.land/p/demo/seqid" "gno.land/p/demo/ufmt" - "gno.land/r/demo/users" + + "gno.land/r/sys/users" ) type ( @@ -62,7 +63,7 @@ func NewGame(addr std.Address) int { } games.Set(gameId.Next().String(), &game{ - player1: std.PrevRealm().Addr(), + player1: std.PreviousRealm().Address(), player2: addr, }) @@ -79,7 +80,7 @@ func Play(idx int) int { roll := rollDice() // Random the player's dice roll // Play the game and update the player's roll - if err := g.play(std.PrevRealm().Addr(), roll); err != nil { + if err := g.play(std.PreviousRealm().Address(), roll); err != nil { panic(err) } @@ -222,9 +223,9 @@ The top players are ranked by performance. Games played against oneself are not // shortName returns a shortened name for the given address func shortName(addr std.Address) string { - user := users.GetUserByAddress(addr) + user := users.ResolveAddress(addr) if user != nil { - return user.Name + return user.Name() } if len(addr) < 10 { return string(addr) @@ -272,7 +273,7 @@ func getPlayer(addr std.Address) *player { // getLeaderBoard generates a leaderboard sorted by points func getLeaderBoard() leaderBoard { board := leaderBoard{} - players.Iterate("", "", func(key string, value interface{}) bool { + players.Iterate("", "", func(key string, value any) bool { player := value.(*player) board = append(board, *player) return false diff --git a/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno b/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno index 2f6770a366f..0b6d9b06333 100644 --- a/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno +++ b/examples/gno.land/r/demo/games/dice_roller/dice_roller_test.gno @@ -27,7 +27,7 @@ func resetGameState() { func TestNewGame(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player2) // Verify that the game has been correctly initialized @@ -43,7 +43,7 @@ func TestNewGame(t *testing.T) { func TestPlay(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player2) g, err := getGame(gameID) @@ -58,7 +58,7 @@ func TestPlay(t *testing.T) { urequire.Equal(t, 0, g.roll2) // Player 2 hasn't rolled yet // Simulate rolling dice for player 2 - std.TestSetOrigCaller(player2) + std.TestSetOriginCaller(player2) roll2 := Play(gameID) // Verify player 2's roll @@ -71,7 +71,7 @@ func TestPlay(t *testing.T) { func TestPlayAgainstSelf(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player1) // Simulate rolling dice twice by the same player @@ -88,11 +88,11 @@ func TestPlayAgainstSelf(t *testing.T) { func TestPlayInvalidPlayer(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player1) // Attempt to play as an invalid player - std.TestSetOrigCaller(unknownPlayer) + std.TestSetOriginCaller(unknownPlayer) urequire.PanicsWithMessage(t, "invalid player", func() { Play(gameID) }) @@ -102,7 +102,7 @@ func TestPlayInvalidPlayer(t *testing.T) { func TestPlayAlreadyPlayed(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player2) // Player 1 rolls @@ -118,21 +118,21 @@ func TestPlayAlreadyPlayed(t *testing.T) { func TestPlayBeyondGameEnd(t *testing.T) { resetGameState() - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) gameID := NewGame(player2) // Play for both players - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) Play(gameID) - std.TestSetOrigCaller(player2) + std.TestSetOriginCaller(player2) Play(gameID) // Check if the game is over - g, err := getGame(gameID) + _, err := getGame(gameID) urequire.NoError(t, err) // Attempt to play more should fail - std.TestSetOrigCaller(player1) + std.TestSetOriginCaller(player1) urequire.PanicsWithMessage(t, "game over", func() { Play(gameID) }) diff --git a/examples/gno.land/r/demo/games/shifumi/shifumi.gno b/examples/gno.land/r/demo/games/shifumi/shifumi.gno index 3de09196da1..698075e5078 100644 --- a/examples/gno.land/r/demo/games/shifumi/shifumi.gno +++ b/examples/gno.land/r/demo/games/shifumi/shifumi.gno @@ -8,7 +8,7 @@ import ( "gno.land/p/demo/avl" "gno.land/p/demo/seqid" - "gno.land/r/demo/users" + "gno.land/r/sys/users" ) const ( @@ -63,7 +63,7 @@ func (g *game) winner() int { // NewGame creates a new game where player1 is the caller and player2 the argument. // A new game index is returned. func NewGame(player std.Address) int { - games.Set(id.Next().String(), &game{player1: std.PrevRealm().Addr(), player2: player}) + games.Set(id.Next().String(), &game{player1: std.PreviousRealm().Address(), player2: player}) return int(id) } @@ -74,7 +74,7 @@ func Play(idx, move int) { if !ok { panic("game not found") } - if err := v.(*game).play(std.PrevRealm().Addr(), move); err != nil { + if err := v.(*game).play(std.PreviousRealm().Address(), move); err != nil { panic(err) } } @@ -109,9 +109,9 @@ Actions: } func shortName(addr std.Address) string { - user := users.GetUserByAddress(addr) + user := users.ResolveAddress(addr) if user != nil { - return user.Name + return user.Name() } if len(addr) < 10 { return string(addr) diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory.gno b/examples/gno.land/r/demo/grc20factory/grc20factory.gno index aa91084ab32..1aaee77f2a1 100644 --- a/examples/gno.land/r/demo/grc20factory/grc20factory.gno +++ b/examples/gno.land/r/demo/grc20factory/grc20factory.gno @@ -21,7 +21,7 @@ type instance struct { } func New(name, symbol string, decimals uint, initialMint, faucet uint64) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() NewWithAdmin(name, symbol, decimals, initialMint, faucet, caller) } @@ -76,21 +76,21 @@ func Allowance(symbol string, owner, spender std.Address) uint64 { func Transfer(symbol string, to std.Address, amount uint64) { inst := mustGetInstance(symbol) - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() teller := inst.ledger.ImpersonateTeller(caller) checkErr(teller.Transfer(to, amount)) } func Approve(symbol string, spender std.Address, amount uint64) { inst := mustGetInstance(symbol) - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() teller := inst.ledger.ImpersonateTeller(caller) checkErr(teller.Approve(spender, amount)) } func TransferFrom(symbol string, from, to std.Address, amount uint64) { inst := mustGetInstance(symbol) - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() teller := inst.ledger.ImpersonateTeller(caller) checkErr(teller.TransferFrom(from, to, amount)) } @@ -103,7 +103,7 @@ func Faucet(symbol string) { } // FIXME: add limits? // FIXME: add payment in gnot? - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() checkErr(inst.ledger.Mint(caller, inst.faucet)) } diff --git a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno index 46fc07fabf2..16584e63a1f 100644 --- a/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno +++ b/examples/gno.land/r/demo/grc20factory/grc20factory_test.gno @@ -10,7 +10,7 @@ import ( ) func TestReadOnlyPublicMethods(t *testing.T) { - std.TestSetOrigPkgAddr("gno.land/r/demo/grc20factory") + std.TestSetOriginPkgAddress("gno.land/r/demo/grc20factory") admin := testutils.TestAddress("admin") bob := testutils.TestAddress("bob") carl := testutils.TestAddress("carl") @@ -36,7 +36,7 @@ func TestReadOnlyPublicMethods(t *testing.T) { } // admin creates FOO and BAR. - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) std.TestSetRealm(std.NewUserRealm(admin)) NewWithAdmin("Foo", "FOO", 3, 1_111_111_000, 5_555, admin) NewWithAdmin("Bar", "BAR", 3, 2_222_000, 6_666, admin) @@ -47,25 +47,25 @@ func TestReadOnlyPublicMethods(t *testing.T) { checkBalances("step2", 1_444_444_000, 1_111_111_000, 333_333_000, 0, 0) // carl uses the faucet. - std.TestSetOrigCaller(carl) + std.TestSetOriginCaller(carl) std.TestSetRealm(std.NewUserRealm(carl)) Faucet("FOO") checkBalances("step3", 1_444_449_555, 1_111_111_000, 333_333_000, 0, 5_555) // admin gives to bob some allowance. - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) std.TestSetRealm(std.NewUserRealm(admin)) Approve("FOO", bob, 1_000_000) checkBalances("step4", 1_444_449_555, 1_111_111_000, 333_333_000, 1_000_000, 5_555) // bob uses a part of the allowance. - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) std.TestSetRealm(std.NewUserRealm(bob)) TransferFrom("FOO", admin, carl, 400_000) checkBalances("step5", 1_444_449_555, 1_110_711_000, 333_333_000, 600_000, 405_555) // bob uses a part of the allowance. - std.TestSetOrigCaller(bob) + std.TestSetOriginCaller(bob) std.TestSetRealm(std.NewUserRealm(bob)) TransferFrom("FOO", admin, carl, 600_000) checkBalances("step6", 1_444_449_555, 1_110_111_000, 333_333_000, 0, 1_005_555) diff --git a/examples/gno.land/r/demo/grc20reg/grc20reg.gno b/examples/gno.land/r/demo/grc20reg/grc20reg.gno index ff46ec94860..9926d6b153e 100644 --- a/examples/gno.land/r/demo/grc20reg/grc20reg.gno +++ b/examples/gno.land/r/demo/grc20reg/grc20reg.gno @@ -12,7 +12,7 @@ import ( var registry = avl.NewTree() // rlmPath[.slug] -> TokenGetter (slug is optional) func Register(tokenGetter grc20.TokenGetter, slug string) { - rlmPath := std.PrevRealm().PkgPath() + rlmPath := std.PreviousRealm().PkgPath() key := fqname.Construct(rlmPath, slug) registry.Set(key, tokenGetter) std.Emit( @@ -44,7 +44,7 @@ func Render(path string) string { // TODO: add pagination s := "" count := 0 - registry.Iterate("", "", func(key string, tokenI interface{}) bool { + registry.Iterate("", "", func(key string, tokenI any) bool { count++ tokenGetter := tokenI.(grc20.TokenGetter) token := tokenGetter() diff --git a/examples/gno.land/r/demo/grc20reg/grc20reg_test.gno b/examples/gno.land/r/demo/grc20reg/grc20reg_test.gno index c93365ff7a1..35aad13abcf 100644 --- a/examples/gno.land/r/demo/grc20reg/grc20reg_test.gno +++ b/examples/gno.land/r/demo/grc20reg/grc20reg_test.gno @@ -13,7 +13,7 @@ func TestRegistry(t *testing.T) { std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/foo")) realmAddr := std.CurrentRealm().PkgPath() token, ledger := grc20.NewToken("TestToken", "TST", 4) - ledger.Mint(std.CurrentRealm().Addr(), 1234567) + ledger.Mint(std.CurrentRealm().Address(), 1234567) tokenGetter := func() *grc20.Token { return token } // register Register(tokenGetter, "") diff --git a/examples/gno.land/r/demo/groups/README.md b/examples/gno.land/r/demo/groups/README.md deleted file mode 100644 index ecdd5065903..00000000000 --- a/examples/gno.land/r/demo/groups/README.md +++ /dev/null @@ -1,24 +0,0 @@ -### - test package - - ./build/gno test examples/gno.land/r/demo/groups/ - -### - add pkg - - ./build/gnokey maketx addpkg -pkgdir "examples/gno.land/r/demo/groups" -deposit 100000000ugnot -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath "gno.land/r/demo/groups" test1 - -### - create group - - ./build/gnokey maketx call -func "CreateGroup" -args "dao_trinity_ngo" -gas-fee "1000000ugnot" -gas-wanted 4000000 -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath "gno.land/r/demo/groups" test1 - -### - add member - - ./build/gnokey maketx call -func "AddMember" -args "1" -args "g1hd3gwzevxlqmd3jsf64mpfczag8a8e5j2wdn3c" -args 12 -args "i am new user" -gas-fee "1000000ugnot" -gas-wanted "4000000" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath "gno.land/r/demo/groups" test1 - -### - delete member - - ./build/gnokey maketx call -func "DeleteMember" -args "1" -args "0" -gas-fee "1000000ugnot" -gas-wanted "4000000" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath "gno.land/r/demo/groups" test1 - -### - delete group - - ./build/gnokey maketx call -func "DeleteGroup" -args "1" -gas-fee "1000000ugnot" -gas-wanted "4000000" -broadcast -chainid dev -remote 0.0.0.0:26657 -pkgpath "gno.land/r/demo/groups" test1 - diff --git a/examples/gno.land/r/demo/groups/group.gno b/examples/gno.land/r/demo/groups/group.gno index a03986e2c76..5081f56f2a9 100644 --- a/examples/gno.land/r/demo/groups/group.gno +++ b/examples/gno.land/r/demo/groups/group.gno @@ -69,7 +69,7 @@ func (group *Group) RenderGroup() string { "Group Last MemberID: " + memberIDKey(group.lastMemberID) + "\n\n" str += "Group Members: \n\n" - group.members.Iterate("", "", func(key string, value interface{}) bool { + group.members.Iterate("", "", func(key string, value any) bool { member := value.(*Member) str += member.getMemberStr() return false diff --git a/examples/gno.land/r/demo/groups/misc.gno b/examples/gno.land/r/demo/groups/misc.gno index 24834b7b60c..479595ab9fd 100644 --- a/examples/gno.land/r/demo/groups/misc.gno +++ b/examples/gno.land/r/demo/groups/misc.gno @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "gno.land/r/demo/users" + "gno.land/r/sys/users" ) //---------------------------------------- @@ -76,19 +76,19 @@ func summaryOf(str string, length int) string { } func displayAddressMD(addr std.Address) string { - user := users.GetUserByAddress(addr) + user := users.ResolveAddress(addr) if user == nil { - return "[" + addr.String() + "](/r/demo/users:" + addr.String() + ")" + return "[" + addr.String() + "](/u/" + addr.String() + ")" } - return "[@" + user.Name + "](/r/demo/users:" + user.Name + ")" + return "[@" + user.Name() + "](/r/gnoland/users/v1:" + user.Name() + ")" } func usernameOf(addr std.Address) string { - user := users.GetUserByAddress(addr) + user := users.ResolveAddress(addr) if user == nil { panic("user not found") } - return user.Name + return user.Name() } func isValidPermission(perm Permission) bool { diff --git a/examples/gno.land/r/demo/groups/public.gno b/examples/gno.land/r/demo/groups/public.gno index 33e7dbdcf35..c4ea7432ec2 100644 --- a/examples/gno.land/r/demo/groups/public.gno +++ b/examples/gno.land/r/demo/groups/public.gno @@ -3,7 +3,7 @@ package groups import ( "std" - "gno.land/r/demo/users" + "gno.land/r/sys/users" ) //---------------------------------------- @@ -19,7 +19,7 @@ func GetGroupIDFromName(name string) (GroupID, bool) { func CreateGroup(name string) GroupID { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OriginCaller() usernameOf(caller) url := "/r/demo/groups:" + name group := newGroup(url, name, caller) @@ -31,13 +31,13 @@ func CreateGroup(name string) GroupID { func AddMember(gid GroupID, address string, weight int, metadata string) MemberID { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OriginCaller() usernameOf(caller) group := getGroup(gid) if !group.HasPermission(caller, EditPermission) { panic("unauthorized to edit group") } - user := users.GetUserByAddress(std.Address(address)) + user := users.ResolveAddress(std.Address(address)) if user == nil { panic("unknown address " + address) } @@ -52,7 +52,7 @@ func AddMember(gid GroupID, address string, weight int, metadata string) MemberI func DeleteGroup(gid GroupID) { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OriginCaller() group := getGroup(gid) if !group.HasPermission(caller, DeletePermission) { panic("unauthorized to delete group") @@ -62,7 +62,7 @@ func DeleteGroup(gid GroupID) { func DeleteMember(gid GroupID, mid MemberID) { std.AssertOriginCall() - caller := std.GetOrigCaller() + caller := std.OriginCaller() group := getGroup(gid) if !group.HasPermission(caller, DeletePermission) { panic("unauthorized to delete member") diff --git a/examples/gno.land/r/demo/groups/render.gno b/examples/gno.land/r/demo/groups/render.gno index 5af714b1ef5..e49aac38b96 100644 --- a/examples/gno.land/r/demo/groups/render.gno +++ b/examples/gno.land/r/demo/groups/render.gno @@ -18,7 +18,7 @@ func RenderGroup(gid GroupID) string { func Render(path string) string { if path == "" { str := "List of all Groups:\n\n" - gGroups.Iterate("", "", func(key string, value interface{}) bool { + gGroups.Iterate("", "", func(key string, value any) bool { group := value.(*Group) str += " * [" + group.name + "](" + group.url + ")\n" return false diff --git a/examples/gno.land/r/demo/groups/z_0_a_filetest.gno b/examples/gno.land/r/demo/groups/z_0_a_filetest.gno index 49ebb5219ec..b31613f5554 100644 --- a/examples/gno.land/r/demo/groups/z_0_a_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_0_a_filetest.gno @@ -1,6 +1,8 @@ // PKGPATH: gno.land/r/demo/groups_test package groups_test +// SEND: 1000000ugnot + import ( "gno.land/r/demo/groups" ) diff --git a/examples/gno.land/r/demo/groups/z_0_b_filetest.gno b/examples/gno.land/r/demo/groups/z_0_b_filetest.gno deleted file mode 100644 index 6d328825dd6..00000000000 --- a/examples/gno.land/r/demo/groups/z_0_b_filetest.gno +++ /dev/null @@ -1,19 +0,0 @@ -// PKGPATH: gno.land/r/demo/groups_test -package groups_test - -import ( - "gno.land/r/demo/groups" - "gno.land/r/demo/users" -) - -var gid groups.GroupID - -func main() { - users.Register("", "gnouser", "my profile") - gid = groups.CreateGroup("test_group") - println(gid) - println(groups.Render("")) -} - -// Error: -// payment must not be less than 20000000 diff --git a/examples/gno.land/r/demo/groups/z_0_c_filetest.gno b/examples/gno.land/r/demo/groups/z_0_c_filetest.gno index 60600e38b78..a5c9702b6c4 100644 --- a/examples/gno.land/r/demo/groups/z_0_c_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_0_c_filetest.gno @@ -1,17 +1,20 @@ // PKGPATH: gno.land/r/demo/groups_test package groups_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/groups" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var gid groups.GroupID func main() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") gid = groups.CreateGroup("test_group") println(gid) println(groups.Render("")) diff --git a/examples/gno.land/r/demo/groups/z_1_a_filetest.gno b/examples/gno.land/r/demo/groups/z_1_a_filetest.gno index 71da1b966ec..51a5737ac4c 100644 --- a/examples/gno.land/r/demo/groups/z_1_a_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_1_a_filetest.gno @@ -1,14 +1,14 @@ // PKGPATH: gno.land/r/demo/groups_test package groups_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( "std" "gno.land/p/demo/testutils" "gno.land/r/demo/groups" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var gid groups.GroupID @@ -16,43 +16,27 @@ var gid groups.GroupID const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main - users.Register("", "gnouser0", "my profile 1") + caller := std.OriginCaller() // main + std.TestSetRealm(std.NewUserRealm(caller)) + users.Register("main123") - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr test1 := testutils.TestAddress("gnouser1") - users.Invite(test1.String()) - // switch to test1 - std.TestSetOrigCaller(test1) - users.Register(caller, "gnouser1", "my other profile 1") + std.TestSetOriginCaller(test1) + std.TestSetRealm(std.NewUserRealm(test1)) + users.Register("test123") - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr test2 := testutils.TestAddress("gnouser2") - users.Invite(test2.String()) - // switch to test1 - std.TestSetOrigCaller(test2) - users.Register(caller, "gnouser2", "my other profile 2") + std.TestSetOriginCaller(test2) + std.TestSetRealm(std.NewUserRealm(test2)) + users.Register("test223") - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr test3 := testutils.TestAddress("gnouser3") - users.Invite(test3.String()) - // switch to test1 - std.TestSetOrigCaller(test3) - users.Register(caller, "gnouser3", "my other profile 3") + std.TestSetOriginCaller(test3) + std.TestSetRealm(std.NewUserRealm(test3)) + users.Register("test323") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) + std.TestSetRealm(std.NewUserRealm(caller)) gid = groups.CreateGroup("test_group") println(gid) @@ -67,7 +51,7 @@ func main() { // // Group Name: test_group // -// Group Creator: gnouser0 +// Group Creator: main123 // // Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 // diff --git a/examples/gno.land/r/demo/groups/z_1_b_filetest.gno b/examples/gno.land/r/demo/groups/z_1_b_filetest.gno index 31a036d4e41..eafa05e2861 100644 --- a/examples/gno.land/r/demo/groups/z_1_b_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_1_b_filetest.gno @@ -1,17 +1,22 @@ // PKGPATH: gno.land/r/demo/groups_test package groups_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/groups" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var gid groups.GroupID func main() { - users.Register("", "gnouser", "my profile") + caller := std.OriginCaller() + std.TestSetRealm(std.NewUserRealm(caller)) + users.Register("gnouser123") + gid = groups.CreateGroup("test_group") println(gid) groups.AddMember(2, "g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy", 55, "metadata3") diff --git a/examples/gno.land/r/demo/groups/z_1_c_filetest.gno b/examples/gno.land/r/demo/groups/z_1_c_filetest.gno index 6eaabb07cdf..b13c88fe1c8 100644 --- a/examples/gno.land/r/demo/groups/z_1_c_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_1_c_filetest.gno @@ -18,8 +18,8 @@ func main() { // add member via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) groups.AddMember(gid, test2.String(), 42, "metadata3") } diff --git a/examples/gno.land/r/demo/groups/z_2_a_filetest.gno b/examples/gno.land/r/demo/groups/z_2_a_filetest.gno index 0c482e1b52f..7b9521f2dc5 100644 --- a/examples/gno.land/r/demo/groups/z_2_a_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_a_filetest.gno @@ -1,14 +1,14 @@ // PKGPATH: gno.land/r/demo/groups_test package groups_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( "std" "gno.land/p/demo/testutils" "gno.land/r/demo/groups" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var gid groups.GroupID @@ -16,46 +16,30 @@ var gid groups.GroupID const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") func main() { - caller := std.GetOrigCaller() // main - users.Register("", "gnouser0", "my profile 1") + caller := std.OriginCaller() // main + std.TestSetRealm(std.NewUserRealm(caller)) + users.Register("main123") - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr test1 := testutils.TestAddress("gnouser1") - users.Invite(test1.String()) - // switch to test1 - std.TestSetOrigCaller(test1) - users.Register(caller, "gnouser1", "my other profile 1") + std.TestSetOriginCaller(test1) + std.TestSetRealm(std.NewUserRealm(test1)) + users.Register("test123") - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr test2 := testutils.TestAddress("gnouser2") - users.Invite(test2.String()) - // switch to test1 - std.TestSetOrigCaller(test2) - users.Register(caller, "gnouser2", "my other profile 2") + std.TestSetOriginCaller(test2) + std.TestSetRealm(std.NewUserRealm(test2)) + users.Register("test223") - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr test3 := testutils.TestAddress("gnouser3") - users.Invite(test3.String()) - // switch to test1 - std.TestSetOrigCaller(test3) - users.Register(caller, "gnouser3", "my other profile 3") + std.TestSetOriginCaller(test3) + std.TestSetRealm(std.NewUserRealm(test3)) + users.Register("test323") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) + std.TestSetRealm(std.NewUserRealm(caller)) gid = groups.CreateGroup("test_group") - println(gid) + println(groups.Render("test_group")) groups.AddMember(gid, test2.String(), 42, "metadata3") @@ -64,12 +48,24 @@ func main() { } // Output: -// 1 // Group ID: 0000000001 // // Group Name: test_group // -// Group Creator: gnouser0 +// Group Creator: main123 +// +// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 +// +// Group Last MemberID: 0000000000 +// +// Group Members: +// +// +// Group ID: 0000000001 +// +// Group Name: test_group +// +// Group Creator: main123 // // Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 // diff --git a/examples/gno.land/r/demo/groups/z_2_b_filetest.gno b/examples/gno.land/r/demo/groups/z_2_b_filetest.gno index fd8e485f16f..1049382a94a 100644 --- a/examples/gno.land/r/demo/groups/z_2_b_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_b_filetest.gno @@ -5,13 +5,13 @@ package groups_test import ( "gno.land/r/demo/groups" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var gid groups.GroupID func main() { - users.Register("", "gnouser", "my profile") + users.Register("gnouser123") gid = groups.CreateGroup("test_group") println(gid) groups.DeleteMember(2, 0) @@ -19,4 +19,4 @@ func main() { } // Error: -// group id (2) does not exists +// user not found diff --git a/examples/gno.land/r/demo/groups/z_2_d_filetest.gno b/examples/gno.land/r/demo/groups/z_2_d_filetest.gno index 3caa726cbd3..500c6c9f80a 100644 --- a/examples/gno.land/r/demo/groups/z_2_d_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_d_filetest.gno @@ -8,24 +8,24 @@ import ( "gno.land/p/demo/testutils" "gno.land/r/demo/groups" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var gid groups.GroupID func main() { - users.Register("", "gnouser", "my profile") + users.Register("gnouser123") gid = groups.CreateGroup("test_group") println(gid) // delete member via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) groups.DeleteMember(gid, 0) println(groups.Render("")) } // Error: -// unauthorized to delete member +// user not found diff --git a/examples/gno.land/r/demo/groups/z_2_e_filetest.gno b/examples/gno.land/r/demo/groups/z_2_e_filetest.gno index ff38acf45a4..b6d2422a18c 100644 --- a/examples/gno.land/r/demo/groups/z_2_e_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_e_filetest.gno @@ -1,17 +1,20 @@ // PKGPATH: gno.land/r/demo/groups_test package groups_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/groups" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var gid groups.GroupID func main() { - users.Register("", "gnouser", "my profile") + std.TestSetRealm(std.NewUserRealm(std.Address("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm"))) // so that CurrentRealm.Addr() matches OrigCaller + users.Register("gnouser123") gid = groups.CreateGroup("test_group") println(gid) groups.DeleteGroup(gid) diff --git a/examples/gno.land/r/demo/groups/z_2_f_filetest.gno b/examples/gno.land/r/demo/groups/z_2_f_filetest.gno index 4fddb768e08..c55af232b5b 100644 --- a/examples/gno.land/r/demo/groups/z_2_f_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_f_filetest.gno @@ -1,18 +1,24 @@ // PKGPATH: gno.land/r/demo/groups_test package groups_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( + "std" + "gno.land/r/demo/groups" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var gid groups.GroupID func main() { - users.Register("", "gnouser", "my profile") + caller := std.OriginCaller() + std.TestSetRealm(std.NewUserRealm(caller)) + users.Register("gnouser123") + gid = groups.CreateGroup("test_group") + println(gid) groups.DeleteGroup(20) println(groups.Render("")) diff --git a/examples/gno.land/r/demo/groups/z_2_g_filetest.gno b/examples/gno.land/r/demo/groups/z_2_g_filetest.gno index 6230b110c74..a89134bb267 100644 --- a/examples/gno.land/r/demo/groups/z_2_g_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_g_filetest.gno @@ -1,27 +1,30 @@ // PKGPATH: gno.land/r/demo/groups_test package groups_test -// SEND: 200000000ugnot +// SEND: 1000000ugnot import ( "std" "gno.land/p/demo/testutils" "gno.land/r/demo/groups" - "gno.land/r/demo/users" + users "gno.land/r/gnoland/users/v1" ) var gid groups.GroupID func main() { - users.Register("", "gnouser", "my profile") + caller := std.OriginCaller() + std.TestSetRealm(std.NewUserRealm(caller)) + users.Register("gnouser123") + gid = groups.CreateGroup("test_group") println(gid) // delete group via anon user test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"ugnot", 9000000}}, nil) + std.TestSetOriginCaller(test2) + std.TestSetOriginSend(std.Coins{{"ugnot", 9000000}}, nil) groups.DeleteGroup(gid) println(groups.Render("")) diff --git a/examples/gno.land/r/demo/keystore/keystore.gno b/examples/gno.land/r/demo/keystore/keystore.gno index 5c76ccd90f8..5c5f9e64e2b 100644 --- a/examples/gno.land/r/demo/keystore/keystore.gno +++ b/examples/gno.land/r/demo/keystore/keystore.gno @@ -33,14 +33,14 @@ type KeyStore struct { // Set will set a value to a key // requires write-access (original caller must be caller) func Set(k, v string) string { - origOwner := std.GetOrigCaller() + origOwner := std.OriginCaller() return set(origOwner.String(), k, v) } // set (private) will set a key to value // requires write-access (original caller must be caller) func set(owner, k, v string) string { - origOwner := std.GetOrigCaller() + origOwner := std.OriginCaller() if origOwner.String() != owner { return StatusNoWriteAccess } @@ -62,14 +62,14 @@ func set(owner, k, v string) string { // Remove removes a key // requires write-access (original owner must be caller) func Remove(k string) string { - origOwner := std.GetOrigCaller() + origOwner := std.OriginCaller() return remove(origOwner.String(), k) } // remove (private) removes a key // requires write-access (original owner must be caller) func remove(owner, k string) string { - origOwner := std.GetOrigCaller() + origOwner := std.OriginCaller() if origOwner.String() != owner { return StatusNoWriteAccess } @@ -94,7 +94,7 @@ func remove(owner, k string) string { // Get returns a value for a key // read-only func Get(k string) string { - origOwner := std.GetOrigCaller() + origOwner := std.OriginCaller() return remove(origOwner.String(), k) } @@ -116,7 +116,7 @@ func get(owner, k string) string { // Size returns size of database // read-only func Size() string { - origOwner := std.GetOrigCaller() + origOwner := std.OriginCaller() return size(origOwner.String()) } @@ -146,7 +146,7 @@ func Render(p string) string { if data.Size() == 0 { return StatusNoDatabases } - data.Iterate("", "", func(key string, value interface{}) bool { + data.Iterate("", "", func(key string, value any) bool { ks := value.(*KeyStore) response += ufmt.Sprintf("- [%s](%s:%s) (%d keys)\n", ks.Owner, BaseURL, ks.Owner, ks.Data.Size()) return false @@ -160,7 +160,7 @@ func Render(p string) string { ks := keystoreInterface.(*KeyStore) i := 0 response += ufmt.Sprintf("# %s database\n\n", ks.Owner) - ks.Data.Iterate("", "", func(key string, value interface{}) bool { + ks.Data.Iterate("", "", func(key string, value any) bool { response += ufmt.Sprintf("- %d [%s](%s:%s:get:%s)\n", i, key, BaseURL, ks.Owner, key) i++ return false diff --git a/examples/gno.land/r/demo/keystore/keystore_test.gno b/examples/gno.land/r/demo/keystore/keystore_test.gno index 9b5fafa2f95..19d2eea16c1 100644 --- a/examples/gno.land/r/demo/keystore/keystore_test.gno +++ b/examples/gno.land/r/demo/keystore/keystore_test.gno @@ -56,13 +56,13 @@ func TestRender(t *testing.T) { p := "" if len(tc.ps) > 0 { p = tc.owner.String() - for i, psv := range tc.ps { + for _, psv := range tc.ps { p += ":" + psv } } p = strings.TrimSuffix(p, ":") t.Run(p, func(t *testing.T) { - std.TestSetOrigCaller(tc.caller) + std.TestSetOriginCaller(tc.caller) var act string if len(tc.ps) > 0 && tc.ps[0] == "set" { act = strings.TrimSpace(Set(tc.ps[1], tc.ps[2])) diff --git a/examples/gno.land/r/demo/microblog/microblog.gno b/examples/gno.land/r/demo/microblog/microblog.gno index 1c3cd5e7d68..86a675b2b80 100644 --- a/examples/gno.land/r/demo/microblog/microblog.gno +++ b/examples/gno.land/r/demo/microblog/microblog.gno @@ -6,12 +6,11 @@ package microblog import ( - "std" "strings" "gno.land/p/demo/microblog" "gno.land/p/demo/ufmt" - "gno.land/r/demo/users" + susers "gno.land/r/sys/users" ) var ( @@ -29,8 +28,8 @@ func renderHome() string { output += "# pages\n\n" for _, page := range m.GetPages() { - if u := users.GetUserByAddress(page.Author); u != nil { - output += ufmt.Sprintf("- [%s (%s)](%s%s)\n", u.Name, page.Author.String(), m.Prefix, page.Author.String()) + if u := susers.ResolveAddress(page.Author); u != nil { + output += ufmt.Sprintf("- [%s (%s)](%s%s)\n", u.Name(), page.Author.String(), m.Prefix, page.Author.String()) } else { output += ufmt.Sprintf("- [%s](%s%s)\n", page.Author.String(), m.Prefix, page.Author.String()) } @@ -67,9 +66,8 @@ func Render(path string) string { func PageToString(p *microblog.Page) string { o := "" - if u := users.GetUserByAddress(p.Author); u != nil { - o += ufmt.Sprintf("# [%s](/r/demo/users:%s)\n\n", u, u) - o += ufmt.Sprintf("%s\n\n", u.Profile) + if u := susers.ResolveAddress(p.Author); u != nil { + o += ufmt.Sprintf("# [%s](/r/gnoland/users/v1:%s)\n\n", u, u) } o += ufmt.Sprintf("## [%s](/r/demo/microblog:%s)\n\n", p.Author, p.Author) @@ -89,9 +87,3 @@ func NewPost(text string) string { } return "added new post" } - -func Register(name, profile string) string { - caller := std.GetOrigCaller() // main - users.Register(caller, name, profile) - return "OK" -} diff --git a/examples/gno.land/r/demo/microblog/microblog_test.gno b/examples/gno.land/r/demo/microblog/microblog_test.gno index 9ad98d3cbfe..e3c1721b9f7 100644 --- a/examples/gno.land/r/demo/microblog/microblog_test.gno +++ b/examples/gno.land/r/demo/microblog/microblog_test.gno @@ -15,7 +15,7 @@ func TestMicroblog(t *testing.T) { author2 std.Address = testutils.TestAddress("author2") ) - std.TestSetOrigCaller(author1) + std.TestSetOriginCaller(author1) urequire.Equal(t, "404", Render("/wrongpath"), "rendering not giving 404") urequire.NotEqual(t, "404", Render(""), "rendering / should not give 404") @@ -27,7 +27,7 @@ func TestMicroblog(t *testing.T) { _, err = m.GetPage("no such author") urequire.Error(t, err, "silo should not exist") - std.TestSetOrigCaller(author2) + std.TestSetOriginCaller(author2) urequire.NoError(t, m.NewPost("hello, web3"), "could not create post") urequire.NoError(t, m.NewPost("hello again, web3"), "could not create post") diff --git a/examples/gno.land/r/demo/nft/nft.gno b/examples/gno.land/r/demo/nft/nft.gno index f4340d6d2a9..fe328a5be17 100644 --- a/examples/gno.land/r/demo/nft/nft.gno +++ b/examples/gno.land/r/demo/nft/nft.gno @@ -74,7 +74,7 @@ func (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) { } func (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) { - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) token, ok := grc.getToken(tid) // Throws if `_tokenId` is not a valid NFT. if !ok { @@ -101,7 +101,7 @@ func (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) { } func (grc *token) Approve(approved std.Address, tid grc721.TokenID) { - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) token, ok := grc.getToken(tid) // Throws if `_tokenId` is not a valid NFT. if !ok { @@ -121,7 +121,7 @@ func (grc *token) Approve(approved std.Address, tid grc721.TokenID) { // XXX make it work for set of operators. func (grc *token) SetApprovalForAll(operator std.Address, approved bool) { - caller := std.GetCallerAt(2) + caller := std.CallerAt(2) grc.operators.Set(caller.String(), operator) } diff --git a/examples/gno.land/r/demo/nft/z_2_filetest.gno b/examples/gno.land/r/demo/nft/z_2_filetest.gno index 91c48bd5957..1848cd8222e 100644 --- a/examples/gno.land/r/demo/nft/z_2_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_2_filetest.gno @@ -9,7 +9,7 @@ import ( ) func main() { - caller := std.GetCallerAt(1) + caller := std.CallerAt(1) addr1 := testutils.TestAddress("addr1") // addr2 := testutils.TestAddress("addr2") grc721 := nft.GetToken() diff --git a/examples/gno.land/r/demo/nft/z_3_filetest.gno b/examples/gno.land/r/demo/nft/z_3_filetest.gno index d0210c8ba4d..09402b38246 100644 --- a/examples/gno.land/r/demo/nft/z_3_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_3_filetest.gno @@ -9,7 +9,7 @@ import ( ) func main() { - caller := std.GetCallerAt(1) + caller := std.CallerAt(1) addr1 := testutils.TestAddress("addr1") addr2 := testutils.TestAddress("addr2") grc721 := nft.GetToken() diff --git a/examples/gno.land/r/demo/nft/z_4_filetest.gno b/examples/gno.land/r/demo/nft/z_4_filetest.gno index b38ce8ea190..3a167f3c87b 100644 --- a/examples/gno.land/r/demo/nft/z_4_filetest.gno +++ b/examples/gno.land/r/demo/nft/z_4_filetest.gno @@ -9,7 +9,7 @@ import ( ) func main() { - caller := std.GetCallerAt(1) + caller := std.CallerAt(1) addr1 := testutils.TestAddress("addr1") addr2 := testutils.TestAddress("addr2") grc721 := nft.GetToken() diff --git a/examples/gno.land/r/demo/profile/profile.gno b/examples/gno.land/r/demo/profile/profile.gno index 1318e19eaf3..6e0baa10929 100644 --- a/examples/gno.land/r/demo/profile/profile.gno +++ b/examples/gno.land/r/demo/profile/profile.gno @@ -70,7 +70,7 @@ var boolFields = map[string]bool{ // Setters func SetStringField(field, value string) bool { - addr := std.PrevRealm().Addr() + addr := std.PreviousRealm().Address() key := addr.String() + ":" + field updated := fields.Set(key, value) @@ -85,7 +85,7 @@ func SetStringField(field, value string) bool { } func SetIntField(field string, value int) bool { - addr := std.PrevRealm().Addr() + addr := std.PreviousRealm().Address() key := addr.String() + ":" + field updated := fields.Set(key, value) @@ -100,7 +100,7 @@ func SetIntField(field string, value int) bool { } func SetBoolField(field string, value bool) bool { - addr := std.PrevRealm().Addr() + addr := std.PreviousRealm().Address() key := addr.String() + ":" + field updated := fields.Set(key, value) diff --git a/examples/gno.land/r/demo/releases_example/example.gno b/examples/gno.land/r/demo/releases_example/example.gno index a2713687d28..74a306b4251 100644 --- a/examples/gno.land/r/demo/releases_example/example.gno +++ b/examples/gno.land/r/demo/releases_example/example.gno @@ -17,7 +17,7 @@ func init() { } func NewRelease(name, url, notes string) { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if caller != admin { panic("restricted area") } @@ -25,7 +25,7 @@ func NewRelease(name, url, notes string) { } func UpdateAdmin(address std.Address) { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if caller != admin { panic("restricted area") } diff --git a/examples/gno.land/r/demo/tamagotchi/realm.gno b/examples/gno.land/r/demo/tamagotchi/realm.gno index f6d648180ed..4222a9c63b5 100644 --- a/examples/gno.land/r/demo/tamagotchi/realm.gno +++ b/examples/gno.land/r/demo/tamagotchi/realm.gno @@ -16,7 +16,7 @@ func init() { func Reset(optionalName string) string { name := optionalName if name == "" { - height := std.GetHeight() + height := std.ChainHeight() name = ufmt.Sprintf("gnome#%d", height) } diff --git a/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno b/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno index d412b6ee6b1..058c81ed034 100644 --- a/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno +++ b/examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno @@ -15,7 +15,7 @@ func (f *fooer) SetS(newVal string) { } func (f *fooer) Foo() { - println("hello " + f.s + " cur=" + std.CurrentRealm().PkgPath() + " prev=" + std.PrevRealm().PkgPath()) + println("hello " + f.s + " cur=" + std.CurrentRealm().PkgPath() + " prev=" + std.PreviousRealm().PkgPath()) } var ( diff --git a/examples/gno.land/r/demo/tests/subtests/subtests.gno b/examples/gno.land/r/demo/tests/subtests/subtests.gno index 6bf43cba5eb..ddfe54cd4b3 100644 --- a/examples/gno.land/r/demo/tests/subtests/subtests.gno +++ b/examples/gno.land/r/demo/tests/subtests/subtests.gno @@ -8,8 +8,8 @@ func GetCurrentRealm() std.Realm { return std.CurrentRealm() } -func GetPrevRealm() std.Realm { - return std.PrevRealm() +func GetPreviousRealm() std.Realm { + return std.PreviousRealm() } func Exec(fn func()) { @@ -21,5 +21,5 @@ func CallAssertOriginCall() { } func CallIsOriginCall() bool { - return std.IsOriginCall() + return std.PreviousRealm().IsUser() } diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index e7fde94ea08..452319c7aca 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -21,10 +21,10 @@ func CurrentRealmPath() string { return std.CurrentRealm().PkgPath() } -var initOrigCaller = std.GetOrigCaller() +var initOriginCaller = std.OriginCaller() -func InitOrigCaller() std.Address { - return initOrigCaller +func InitOriginCaller() std.Address { + return initOriginCaller } func CallAssertOriginCall() { @@ -32,7 +32,7 @@ func CallAssertOriginCall() { } func CallIsOriginCall() bool { - return std.IsOriginCall() + return std.PreviousRealm().IsUser() } func CallSubtestsAssertOriginCall() { @@ -91,12 +91,12 @@ func PrintTestNodes() { println(gTestNode2.Child.Name) } -func GetPrevRealm() std.Realm { - return std.PrevRealm() +func GetPreviousRealm() std.Realm { + return std.PreviousRealm() } -func GetRSubtestsPrevRealm() std.Realm { - return rsubtests.GetPrevRealm() +func GetRSubtestsPreviousRealm() std.Realm { + return rsubtests.GetPreviousRealm() } func Exec(fn func()) { diff --git a/examples/gno.land/r/demo/tests/tests_test.gno b/examples/gno.land/r/demo/tests/tests_test.gno index ccbc6b91265..e5851f317b9 100644 --- a/examples/gno.land/r/demo/tests/tests_test.gno +++ b/examples/gno.land/r/demo/tests/tests_test.gno @@ -1,17 +1,23 @@ -package tests +package tests_test import ( "std" "testing" + + "gno.land/p/demo/testutils" + "gno.land/r/demo/tests" ) func TestAssertOriginCall(t *testing.T) { // CallAssertOriginCall(): no panic - CallAssertOriginCall() - if !CallIsOriginCall() { + caller := testutils.TestAddress("caller") + std.TestSetRealm(std.NewUserRealm(caller)) + tests.CallAssertOriginCall() + if !tests.CallIsOriginCall() { t.Errorf("expected IsOriginCall=true but got false") } + std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/tests")) // CallAssertOriginCall() from a block: panic expectedReason := "invalid non-origin call" func() { @@ -23,10 +29,10 @@ func TestAssertOriginCall(t *testing.T) { }() // if called inside a function literal, this is no longer an origin call // because there's one additional frame (the function literal block). - if CallIsOriginCall() { + if tests.CallIsOriginCall() { t.Errorf("expected IsOriginCall=false but got true") } - CallAssertOriginCall() + tests.CallAssertOriginCall() }() // CallSubtestsAssertOriginCall(): panic @@ -36,23 +42,24 @@ func TestAssertOriginCall(t *testing.T) { t.Errorf("expected panic with '%v', got '%v'", expectedReason, r) } }() - if CallSubtestsIsOriginCall() { + if tests.CallSubtestsIsOriginCall() { t.Errorf("expected IsOriginCall=false but got true") } - CallSubtestsAssertOriginCall() + tests.CallSubtestsAssertOriginCall() } -func TestPrevRealm(t *testing.T) { +func TestPreviousRealm(t *testing.T) { var ( - user1Addr = std.DerivePkgAddr("user1.gno") + firstRealm = std.DerivePkgAddr("gno.land/r/demo/tests_test") rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) - // When a single realm in the frames, PrevRealm returns the user - if addr := GetPrevRealm().Addr(); addr != user1Addr { - t.Errorf("want GetPrevRealm().Addr==%s, got %s", user1Addr, addr) + // When only one realm in the frames, PreviousRealm returns the same realm + if addr := tests.GetPreviousRealm().Address(); addr != firstRealm { + println(tests.GetPreviousRealm()) + t.Errorf("want GetPreviousRealm().Address==%s, got %s", firstRealm, addr) } - // When 2 or more realms in the frames, PrevRealm returns the second to last - if addr := GetRSubtestsPrevRealm().Addr(); addr != rTestsAddr { - t.Errorf("want GetRSubtestsPrevRealm().Addr==%s, got %s", rTestsAddr, addr) + // When 2 or more realms in the frames, PreviousRealm returns the second to last + if addr := tests.GetRSubtestsPreviousRealm().Address(); addr != rTestsAddr { + t.Errorf("want GetRSubtestsPreviousRealm().Address==%s, got %s", rTestsAddr, addr) } } diff --git a/examples/gno.land/r/demo/tests/z2_filetest.gno b/examples/gno.land/r/demo/tests/z2_filetest.gno index 147d2c12c6c..bad59e0a32b 100644 --- a/examples/gno.land/r/demo/tests/z2_filetest.gno +++ b/examples/gno.land/r/demo/tests/z2_filetest.gno @@ -7,18 +7,18 @@ import ( "gno.land/r/demo/tests" ) -// When a single realm in the frames, PrevRealm returns the user -// When 2 or more realms in the frames, PrevRealm returns the second to last +// When a single realm in the frames, PreviousRealm returns the user +// When 2 or more realms in the frames, PreviousRealm returns the second to last func main() { var ( eoa = testutils.TestAddress("someone") rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) - std.TestSetOrigCaller(eoa) - println("tests.GetPrevRealm().Addr(): ", tests.GetPrevRealm().Addr()) - println("tests.GetRSubtestsPrevRealm().Addr(): ", tests.GetRSubtestsPrevRealm().Addr()) + std.TestSetOriginCaller(eoa) + println("tests.GetPreviousRealm().Address(): ", tests.GetPreviousRealm().Address()) + println("tests.GetRSubtestsPreviousRealm().Address(): ", tests.GetRSubtestsPreviousRealm().Address()) } // Output: -// tests.GetPrevRealm().Addr(): g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk -// tests.GetRSubtestsPrevRealm().Addr(): g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq +// tests.GetPreviousRealm().Address(): g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk +// tests.GetRSubtestsPreviousRealm().Address(): g1gz4ycmx0s6ln2wdrsh4e00l9fsel2wskqa3snq diff --git a/examples/gno.land/r/demo/tests/z3_filetest.gno b/examples/gno.land/r/demo/tests/z3_filetest.gno index 5430e7f7151..430486e2818 100644 --- a/examples/gno.land/r/demo/tests/z3_filetest.gno +++ b/examples/gno.land/r/demo/tests/z3_filetest.gno @@ -13,16 +13,16 @@ func main() { eoa = testutils.TestAddress("someone") rTestsAddr = std.DerivePkgAddr("gno.land/r/demo/tests") ) - std.TestSetOrigCaller(eoa) - // Contrarily to z2_filetest.gno we EXPECT GetPrevRealms != eoa (#1704) - if addr := tests.GetPrevRealm().Addr(); addr != eoa { - println("want tests.GetPrevRealm().Addr ==", eoa, "got", addr) + std.TestSetOriginCaller(eoa) + // Contrarily to z2_filetest.gno we EXPECT GetPreviousRealms != eoa (#1704) + if addr := tests.GetPreviousRealm().Address(); addr != eoa { + println("want tests.GetPreviousRealm().Addr ==", eoa, "got", addr) } // When 2 or more realms in the frames, it is also different - if addr := tests.GetRSubtestsPrevRealm().Addr(); addr != rTestsAddr { - println("want GetRSubtestsPrevRealm().Addr ==", rTestsAddr, "got", addr) + if addr := tests.GetRSubtestsPreviousRealm().Address(); addr != rTestsAddr { + println("want GetRSubtestsPreviousRealm().Addr ==", rTestsAddr, "got", addr) } } // Output: -// want tests.GetPrevRealm().Addr == g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk got g1xufrdvnfk6zc9r0nqa23ld3tt2r5gkyvw76q63 +// want tests.GetPreviousRealm().Addr == g1wdhk6et0dej47h6lta047h6lta047h6lrnerlk got g1xufrdvnfk6zc9r0nqa23ld3tt2r5gkyvw76q63 diff --git a/examples/gno.land/r/demo/todolist/todolist.gno b/examples/gno.land/r/demo/todolist/todolist.gno index 5790aac630a..217dbce2855 100644 --- a/examples/gno.land/r/demo/todolist/todolist.gno +++ b/examples/gno.land/r/demo/todolist/todolist.gno @@ -121,7 +121,7 @@ func renderHomepage() string { } // Iterate through AVL tree - todolistTree.Iterate("", "", func(key string, value interface{}) bool { + todolistTree.Iterate("", "", func(key string, value any) bool { // cast raw data from tree into Todolist struct tl := value.(*todolist.TodoList) diff --git a/examples/gno.land/r/demo/todolist/todolist_test.gno b/examples/gno.land/r/demo/todolist/todolist_test.gno index 6446732df3e..90756c9dbe3 100644 --- a/examples/gno.land/r/demo/todolist/todolist_test.gno +++ b/examples/gno.land/r/demo/todolist/todolist_test.gno @@ -10,7 +10,7 @@ import ( ) var ( - node interface{} + node any tdl *todolist.TodoList ) @@ -26,7 +26,7 @@ func TestNewTodoList(t *testing.T) { uassert.Equal(t, title, tdl.Title, "title does not match") uassert.Equal(t, 1, tlid, "tlid does not match") - uassert.Equal(t, tdl.Owner.String(), std.GetOrigCaller().String(), "owner does not match") + uassert.Equal(t, tdl.Owner.String(), std.OriginCaller().String(), "owner does not match") uassert.Equal(t, 0, len(tdl.GetTasks()), "Expected no tasks in the todo list") } diff --git a/examples/gno.land/r/demo/types/types.gno b/examples/gno.land/r/demo/types/types.gno index 9c729742e03..5e268c46503 100644 --- a/examples/gno.land/r/demo/types/types.gno +++ b/examples/gno.land/r/demo/types/types.gno @@ -17,7 +17,7 @@ var ( gIntSlice []int = []int{-42, 0, 42} gUintSlice []uint = []uint{0, 42, 84} gTree avl.Tree - // gInterface = interface{}{-42, "a string", uint(42)} + // gInterface = any{-42, "a string", uint(42)} ) func init() { diff --git a/examples/gno.land/r/demo/userbook/render.gno b/examples/gno.land/r/demo/userbook/render.gno index 94f7567cbf4..035dd060185 100644 --- a/examples/gno.land/r/demo/userbook/render.gno +++ b/examples/gno.land/r/demo/userbook/render.gno @@ -4,14 +4,14 @@ package userbook import ( "strconv" - "gno.land/r/demo/users" + "gno.land/r/sys/users" "gno.land/p/demo/avl/pager" "gno.land/p/demo/ufmt" "gno.land/p/moul/txlink" ) -const usersLink = "/r/demo/users" +const usersLink = "/r/gnoland/users/v1" func Render(path string) string { p := pager.NewPager(signupsTree, 20, true) @@ -26,8 +26,8 @@ func Render(path string) string { signup := item.Value.(*Signup) user := signup.address.String() - if data := users.GetUserByAddress(signup.address); data != nil { - user = ufmt.Sprintf("[%s](%s:%s)", data.Name, usersLink, data.Name) + if data := users.ResolveAddress(signup.address); data != nil { + user = ufmt.Sprintf("[%s](%s:%s)", data.Name(), usersLink, data.Name()) } out += ufmt.Sprintf("- **User #%d - %s - signed up on %s**\n\n", signup.ordinal, user, signup.timestamp.Format("January 2 2006, 03:04:04 PM")) diff --git a/examples/gno.land/r/demo/userbook/userbook.gno b/examples/gno.land/r/demo/userbook/userbook.gno index 03027f064b0..412f9410318 100644 --- a/examples/gno.land/r/demo/userbook/userbook.gno +++ b/examples/gno.land/r/demo/userbook/userbook.gno @@ -30,7 +30,7 @@ func init() { func SignUp() string { // Get transaction caller - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() // Check if the user is already signed up if _, exists := tracker.Get(caller.String()); exists { diff --git a/examples/gno.land/r/demo/users/gno.mod b/examples/gno.land/r/demo/users/gno.mod deleted file mode 100644 index 4d7fd15d1cd..00000000000 --- a/examples/gno.land/r/demo/users/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/demo/users diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno deleted file mode 100644 index 451afc7bf96..00000000000 --- a/examples/gno.land/r/demo/users/users.gno +++ /dev/null @@ -1,350 +0,0 @@ -package users - -import ( - "regexp" - "std" - "strconv" - "strings" - - "gno.land/p/demo/avl" - "gno.land/p/demo/avl/pager" - "gno.land/p/demo/avlhelpers" - "gno.land/p/demo/users" -) - -//---------------------------------------- -// State - -var ( - admin std.Address = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul - - restricted avl.Tree // Name -> true - restricted name - name2User avl.Tree // Name -> *users.User - addr2User avl.Tree // std.Address -> *users.User - invites avl.Tree // string(inviter+":"+invited) -> true - counter int // user id counter - minFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register. - maxFeeMult int64 = 10 // maximum multiples of minFee accepted. -) - -//---------------------------------------- -// Top-level functions - -func Register(inviter std.Address, name string, profile string) { - // assert CallTx call. - std.AssertOriginCall() - // assert invited or paid. - caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { - panic("should not happen") // because std.AssertOrigCall(). - } - - sentCoins := std.GetOrigSend() - minCoin := std.NewCoin("ugnot", minFee) - - if inviter == "" { - // banker := std.GetBanker(std.BankerTypeOrigSend) - if len(sentCoins) == 1 && sentCoins[0].IsGTE(minCoin) { - if sentCoins[0].Amount > minFee*maxFeeMult { - panic("payment must not be greater than " + strconv.Itoa(int(minFee*maxFeeMult))) - } else { - // ok - } - } else { - panic("payment must not be less than " + strconv.Itoa(int(minFee))) - } - } else { - invitekey := inviter.String() + ":" + caller.String() - _, ok := invites.Get(invitekey) - if !ok { - panic("invalid invitation") - } - invites.Remove(invitekey) - } - - // assert not already registered. - _, ok := name2User.Get(name) - if ok { - panic("name already registered: " + name) - } - _, ok = addr2User.Get(caller.String()) - if ok { - panic("address already registered: " + caller.String()) - } - - isInviterAdmin := inviter == admin - - // check for restricted name - if _, isRestricted := restricted.Get(name); isRestricted { - // only address invite by the admin can register restricted name - if !isInviterAdmin { - panic("restricted name: " + name) - } - - restricted.Remove(name) - } - - // assert name is valid. - // admin inviter can bypass name restriction - if !isInviterAdmin && !reName.MatchString(name) { - panic("invalid name: " + name + " (must be at least 6 characters, lowercase alphanumeric with underscore)") - } - - // remainder of fees go toward invites. - invites := int(0) - if len(sentCoins) == 1 { - if sentCoins[0].Denom == "ugnot" && sentCoins[0].Amount >= minFee { - invites = int(sentCoins[0].Amount / minFee) - if inviter == "" && invites > 0 { - invites -= 1 - } - } - } - // register. - counter++ - user := &users.User{ - Address: caller, - Name: name, - Profile: profile, - Number: counter, - Invites: invites, - Inviter: inviter, - } - name2User.Set(name, user) - addr2User.Set(caller.String(), user) -} - -func Invite(invitee string) { - // assert CallTx call. - std.AssertOriginCall() - // get caller/inviter. - caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { - panic("should not happen") // because std.AssertOrigCall(). - } - lines := strings.Split(invitee, "\n") - if caller == admin { - // nothing to do, all good - } else { - // ensure has invites. - userI, ok := addr2User.Get(caller.String()) - if !ok { - panic("user unknown") - } - user := userI.(*users.User) - if user.Invites <= 0 { - panic("user has no invite tokens") - } - user.Invites -= len(lines) - if user.Invites < 0 { - panic("user has insufficient invite tokens") - } - } - // for each line... - for _, line := range lines { - if line == "" { - continue // file bodies have a trailing newline. - } else if strings.HasPrefix(line, `//`) { - continue // comment - } - // record invite. - invitekey := string(caller) + ":" + string(line) - invites.Set(invitekey, true) - } -} - -func GrantInvites(invites string) { - // assert CallTx call. - std.AssertOriginCall() - // assert admin. - caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { - panic("should not happen") // because std.AssertOrigCall(). - } - if caller != admin { - panic("unauthorized") - } - // for each line... - lines := strings.Split(invites, "\n") - for _, line := range lines { - if line == "" { - continue // file bodies have a trailing newline. - } else if strings.HasPrefix(line, `//`) { - continue // comment - } - // parse name and invites. - var name string - var invites int - parts := strings.Split(line, ":") - if len(parts) == 1 { // short for :1. - name = parts[0] - invites = 1 - } else if len(parts) == 2 { - name = parts[0] - invites_, err := strconv.Atoi(parts[1]) - if err != nil { - panic(err) - } - invites = int(invites_) - } else { - panic("should not happen") - } - // give invites. - userI, ok := name2User.Get(name) - if !ok { - // maybe address. - userI, ok = addr2User.Get(name) - if !ok { - panic("invalid user " + name) - } - } - user := userI.(*users.User) - user.Invites += invites - } -} - -// Any leftover fees go toward invitations. -func SetMinFee(newMinFee int64) { - // assert CallTx call. - std.AssertOriginCall() - // assert admin caller. - caller := std.GetCallerAt(2) - if caller != admin { - panic("unauthorized") - } - // update global variables. - minFee = newMinFee -} - -// This helps prevent fat finger accidents. -func SetMaxFeeMultiple(newMaxFeeMult int64) { - // assert CallTx call. - std.AssertOriginCall() - // assert admin caller. - caller := std.GetCallerAt(2) - if caller != admin { - panic("unauthorized") - } - // update global variables. - maxFeeMult = newMaxFeeMult -} - -//---------------------------------------- -// Exposed public functions - -func GetUserByName(name string) *users.User { - userI, ok := name2User.Get(name) - if !ok { - return nil - } - return userI.(*users.User) -} - -func GetUserByAddress(addr std.Address) *users.User { - userI, ok := addr2User.Get(addr.String()) - if !ok { - return nil - } - return userI.(*users.User) -} - -// unlike GetUserByName, input must be "@" prefixed for names. -func GetUserByAddressOrName(input users.AddressOrName) *users.User { - name, isName := input.GetName() - if isName { - return GetUserByName(name) - } - return GetUserByAddress(std.Address(input)) -} - -// Get a list of user names starting from the given prefix. Limit the -// number of results to maxResults. (This can be used for a name search tool.) -func ListUsersByPrefix(prefix string, maxResults int) []string { - return avlhelpers.ListByteStringKeysByPrefix(&name2User, prefix, maxResults) -} - -func Resolve(input users.AddressOrName) std.Address { - name, isName := input.GetName() - if !isName { - return std.Address(input) // TODO check validity - } - - user := GetUserByName(name) - return user.Address -} - -// Add restricted name to the list -func AdminAddRestrictedName(name string) { - // assert CallTx call. - std.AssertOriginCall() - // get caller - caller := std.GetOrigCaller() - // assert admin - if caller != admin { - panic("unauthorized") - } - - if user := GetUserByName(name); user != nil { - panic("already registered name") - } - - // register restricted name - - restricted.Set(name, true) -} - -//---------------------------------------- -// Constants - -// NOTE: name length must be clearly distinguishable from a bech32 address. -var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`) - -//---------------------------------------- -// Render main page - -func Render(fullPath string) string { - path, _ := splitPathAndQuery(fullPath) - if path == "" { - return renderHome(fullPath) - } else if len(path) >= 38 { // 39? 40? - if path[:2] != "g1" { - return "invalid address " + path - } - user := GetUserByAddress(std.Address(path)) - if user == nil { - // TODO: display basic information about account. - return "unknown address " + path - } - return user.Render() - } else { - user := GetUserByName(path) - if user == nil { - return "unknown username " + path - } - return user.Render() - } -} - -func renderHome(path string) string { - doc := "" - - page := pager.NewPager(&name2User, 50, false).MustGetPageByPath(path) - - for _, item := range page.Items { - user := item.Value.(*users.User) - doc += " * [" + user.Name + "](/r/demo/users:" + user.Name + ")\n" - } - doc += "\n" - doc += page.Picker() - return doc -} - -func splitPathAndQuery(fullPath string) (string, string) { - parts := strings.SplitN(fullPath, "?", 2) - path := parts[0] - queryString := "" - if len(parts) > 1 { - queryString = "?" + parts[1] - } - return path, queryString -} diff --git a/examples/gno.land/r/demo/users/users_test.gno b/examples/gno.land/r/demo/users/users_test.gno deleted file mode 100644 index 864793dc514..00000000000 --- a/examples/gno.land/r/demo/users/users_test.gno +++ /dev/null @@ -1,13 +0,0 @@ -package users - -import ( - "testing" - - "gno.land/p/demo/uassert" -) - -func TestPreRegisteredTest1(t *testing.T) { - names := ListUsersByPrefix("test1", 1) - uassert.Equal(t, len(names), 1) - uassert.Equal(t, names[0], "test1") -} diff --git a/examples/gno.land/r/demo/users/z_0_b_filetest.gno b/examples/gno.land/r/demo/users/z_0_b_filetest.gno deleted file mode 100644 index c33edc32985..00000000000 --- a/examples/gno.land/r/demo/users/z_0_b_filetest.gno +++ /dev/null @@ -1,15 +0,0 @@ -package main - -// SEND: 19900000ugnot - -import ( - "gno.land/r/demo/users" -) - -func main() { - users.Register("", "gnouser", "my profile") - println("done") -} - -// Error: -// payment must not be less than 20000000 diff --git a/examples/gno.land/r/demo/users/z_0_filetest.gno b/examples/gno.land/r/demo/users/z_0_filetest.gno deleted file mode 100644 index cbb2e9209f4..00000000000 --- a/examples/gno.land/r/demo/users/z_0_filetest.gno +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "std" - - "gno.land/r/demo/users" -) - -func main() { - std.TestSetOrigSend(std.Coins{std.NewCoin("dontcare", 1)}, nil) - users.Register("", "gnouser", "my profile") - println("done") -} - -// Error: -// incompatible coin denominations: dontcare, ugnot diff --git a/examples/gno.land/r/demo/users/z_10_filetest.gno b/examples/gno.land/r/demo/users/z_10_filetest.gno deleted file mode 100644 index afeecffcc42..00000000000 --- a/examples/gno.land/r/demo/users/z_10_filetest.gno +++ /dev/null @@ -1,33 +0,0 @@ -// PKGPATH: gno.land/r/demo/users_test -package users_test - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func init() { - caller := std.GetOrigCaller() // main - test2 := testutils.TestAddress("test2") - // as admin, invite gnouser and test2 - std.TestSetOrigCaller(admin) - users.Invite(caller.String() + "\n" + test2.String()) - // register as caller - std.TestSetOrigCaller(caller) - users.Register(admin, "gnouser", "my profile") -} - -func main() { - // register as test2 - test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) - users.Register(admin, "test222", "my profile 2") - println("done") -} - -// Output: -// done diff --git a/examples/gno.land/r/demo/users/z_11_filetest.gno b/examples/gno.land/r/demo/users/z_11_filetest.gno deleted file mode 100644 index 27c7e9813da..00000000000 --- a/examples/gno.land/r/demo/users/z_11_filetest.gno +++ /dev/null @@ -1,25 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "std" - - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() // main - std.TestSetOrigCaller(admin) - users.AdminAddRestrictedName("superrestricted") - - // test restricted name - std.TestSetOrigCaller(caller) - users.Register("", "superrestricted", "my profile") - println("done") -} - -// Error: -// restricted name: superrestricted diff --git a/examples/gno.land/r/demo/users/z_11b_filetest.gno b/examples/gno.land/r/demo/users/z_11b_filetest.gno deleted file mode 100644 index be508963911..00000000000 --- a/examples/gno.land/r/demo/users/z_11b_filetest.gno +++ /dev/null @@ -1,28 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "std" - - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() // main - std.TestSetOrigCaller(admin) - // add restricted name - users.AdminAddRestrictedName("superrestricted") - // grant invite to caller - users.Invite(caller.String()) - // set back caller - std.TestSetOrigCaller(caller) - // register restricted name with admin invite - users.Register(admin, "superrestricted", "my profile") - println("done") -} - -// Output: -// done diff --git a/examples/gno.land/r/demo/users/z_12_filetest.gno b/examples/gno.land/r/demo/users/z_12_filetest.gno deleted file mode 100644 index 0fb7d27bd34..00000000000 --- a/examples/gno.land/r/demo/users/z_12_filetest.gno +++ /dev/null @@ -1,49 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "strconv" - - "gno.land/r/demo/users" -) - -func main() { - users.Register("", "alicia", "my profile") - - { - // Normal usage - names := users.ListUsersByPrefix("a", 1) - println("# names: " + strconv.Itoa(len(names))) - println("name: " + names[0]) - } - - { - // Empty prefix: match all - names := users.ListUsersByPrefix("", 1) - println("# names: " + strconv.Itoa(len(names))) - println("name: " + names[0]) - } - - { - // The prefix is before "alicia" - names := users.ListUsersByPrefix("alich", 1) - println("# names: " + strconv.Itoa(len(names))) - } - - { - // The prefix is after the last name - names := users.ListUsersByPrefix("y", 10) - println("# names: " + strconv.Itoa(len(names))) - } - - // More tests are in p/demo/avlhelpers -} - -// Output: -// # names: 1 -// name: alicia -// # names: 1 -// name: alicia -// # names: 0 -// # names: 0 diff --git a/examples/gno.land/r/demo/users/z_13_filetest.gno b/examples/gno.land/r/demo/users/z_13_filetest.gno deleted file mode 100644 index 6ef312dc41c..00000000000 --- a/examples/gno.land/r/demo/users/z_13_filetest.gno +++ /dev/null @@ -1,22 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "strconv" - - "gno.land/r/demo/users" -) - -func main() { - { - // Verify pre-registered test1 user - names := users.ListUsersByPrefix("test1", 1) - println("# names: " + strconv.Itoa(len(names))) - println("name: " + names[0]) - } -} - -// Output: -// # names: 1 -// name: test1 diff --git a/examples/gno.land/r/demo/users/z_1_filetest.gno b/examples/gno.land/r/demo/users/z_1_filetest.gno deleted file mode 100644 index 504a0c7c3f9..00000000000 --- a/examples/gno.land/r/demo/users/z_1_filetest.gno +++ /dev/null @@ -1,15 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "gno.land/r/demo/users" -) - -func main() { - users.Register("", "gnouser", "my profile") - println("done") -} - -// Output: -// done diff --git a/examples/gno.land/r/demo/users/z_2_filetest.gno b/examples/gno.land/r/demo/users/z_2_filetest.gno deleted file mode 100644 index c1b92790f8b..00000000000 --- a/examples/gno.land/r/demo/users/z_2_filetest.gno +++ /dev/null @@ -1,32 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() // main - users.Register("", "gnouser", "my profile") - // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr - test1 := testutils.TestAddress("test1") - users.Invite(test1.String()) - // switch to test1 - std.TestSetOrigCaller(test1) - users.Register(caller, "satoshi", "my other profile") - println("done") -} - -// Output: -// done diff --git a/examples/gno.land/r/demo/users/z_3_filetest.gno b/examples/gno.land/r/demo/users/z_3_filetest.gno deleted file mode 100644 index 5402235e03d..00000000000 --- a/examples/gno.land/r/demo/users/z_3_filetest.gno +++ /dev/null @@ -1,33 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() // main - users.Register("", "gnouser", "my profile") - // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr - test1 := testutils.TestAddress("test1") - users.Invite(test1.String()) - // switch to test1 - std.TestSetOrigCaller(test1) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) - users.Register(caller, "satoshi", "my other profile") - println("done") -} - -// Output: -// done diff --git a/examples/gno.land/r/demo/users/z_4_filetest.gno b/examples/gno.land/r/demo/users/z_4_filetest.gno deleted file mode 100644 index 613fadf9625..00000000000 --- a/examples/gno.land/r/demo/users/z_4_filetest.gno +++ /dev/null @@ -1,34 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() // main - users.Register("", "gnouser", "my profile") - // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr - test1 := testutils.TestAddress("test1") - test2 := testutils.TestAddress("test2") - users.Invite(test1.String()) - // switch to test2 (not test1) - std.TestSetOrigCaller(test2) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) - users.Register(caller, "satoshi", "my other profile") - println("done") -} - -// Error: -// invalid invitation diff --git a/examples/gno.land/r/demo/users/z_5_filetest.gno b/examples/gno.land/r/demo/users/z_5_filetest.gno deleted file mode 100644 index 6465cc9c378..00000000000 --- a/examples/gno.land/r/demo/users/z_5_filetest.gno +++ /dev/null @@ -1,76 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() // main - users.Register("", "gnouser", "my profile") - // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr - test1 := testutils.TestAddress("test1") - users.Invite(test1.String()) - // switch to test1 - std.TestSetOrigCaller(test1) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) - users.Register(caller, "satoshi", "my other profile") - println(users.Render("")) - println("========================================") - println(users.Render("?page=2")) - println("========================================") - println(users.Render("gnouser")) - println("========================================") - println(users.Render("satoshi")) - println("========================================") - println(users.Render("badname")) -} - -// Output: -// * [archives](/r/demo/users:archives) -// * [demo](/r/demo/users:demo) -// * [gno](/r/demo/users:gno) -// * [gnoland](/r/demo/users:gnoland) -// * [gnolang](/r/demo/users:gnolang) -// * [gnouser](/r/demo/users:gnouser) -// * [gov](/r/demo/users:gov) -// * [nt](/r/demo/users:nt) -// * [satoshi](/r/demo/users:satoshi) -// * [sys](/r/demo/users:sys) -// * [test1](/r/demo/users:test1) -// * [x](/r/demo/users:x) -// -// -// ======================================== -// -// -// ======================================== -// ## user gnouser -// -// * address = g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm -// * 9 invites -// -// my profile -// -// ======================================== -// ## user satoshi -// -// * address = g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 -// * 0 invites -// * invited by g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm -// -// my other profile -// -// ======================================== -// unknown username badname diff --git a/examples/gno.land/r/demo/users/z_6_filetest.gno b/examples/gno.land/r/demo/users/z_6_filetest.gno deleted file mode 100644 index 919088088a2..00000000000 --- a/examples/gno.land/r/demo/users/z_6_filetest.gno +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "std" - - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() - // as admin, grant invites to unregistered user. - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - println("done") -} - -// Error: -// invalid user g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm diff --git a/examples/gno.land/r/demo/users/z_7_filetest.gno b/examples/gno.land/r/demo/users/z_7_filetest.gno deleted file mode 100644 index 1d3c9e3a917..00000000000 --- a/examples/gno.land/r/demo/users/z_7_filetest.gno +++ /dev/null @@ -1,36 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() // main - users.Register("", "gnouser", "my profile") - // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr - test1 := testutils.TestAddress("test1") - users.Invite(test1.String()) - // switch to test1 - std.TestSetOrigCaller(test1) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) - users.Register(caller, "satoshi", "my other profile") - // as admin, grant invites to gnouser(again) and satoshi. - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1\n" + test1.String() + ":1") - println("done") -} - -// Output: -// done diff --git a/examples/gno.land/r/demo/users/z_7b_filetest.gno b/examples/gno.land/r/demo/users/z_7b_filetest.gno deleted file mode 100644 index 09c15bb135d..00000000000 --- a/examples/gno.land/r/demo/users/z_7b_filetest.gno +++ /dev/null @@ -1,36 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() // main - users.Register("", "gnouser", "my profile") - // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1\n") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr - test1 := testutils.TestAddress("test1") - users.Invite(test1.String()) - // switch to test1 - std.TestSetOrigCaller(test1) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) - users.Register(caller, "satoshi", "my other profile") - // as admin, grant invites to gnouser(again) and satoshi. - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1\n" + test1.String() + ":1") - println("done") -} - -// Output: -// done diff --git a/examples/gno.land/r/demo/users/z_8_filetest.gno b/examples/gno.land/r/demo/users/z_8_filetest.gno deleted file mode 100644 index 78fada74a71..00000000000 --- a/examples/gno.land/r/demo/users/z_8_filetest.gno +++ /dev/null @@ -1,37 +0,0 @@ -package main - -// SEND: 200000000ugnot - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() // main - users.Register("", "gnouser", "my profile") - // as admin, grant invites to gnouser - std.TestSetOrigCaller(admin) - users.GrantInvites(caller.String() + ":1") - // switch back to caller - std.TestSetOrigCaller(caller) - // invite another addr - test1 := testutils.TestAddress("test1") - users.Invite(test1.String()) - // switch to test1 - std.TestSetOrigCaller(test1) - std.TestSetOrigSend(std.Coins{{"dontcare", 1}}, nil) - users.Register(caller, "satoshi", "my other profile") - // as admin, grant invites to gnouser(again) and nonexistent user. - std.TestSetOrigCaller(admin) - test2 := testutils.TestAddress("test2") - users.GrantInvites(caller.String() + ":1\n" + test2.String() + ":1") - println("done") -} - -// Error: -// invalid user g1w3jhxapjta047h6lta047h6lta047h6laqcyu4 diff --git a/examples/gno.land/r/demo/users/z_9_filetest.gno b/examples/gno.land/r/demo/users/z_9_filetest.gno deleted file mode 100644 index c73c685aebd..00000000000 --- a/examples/gno.land/r/demo/users/z_9_filetest.gno +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/users" -) - -const admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") - -func main() { - caller := std.GetOrigCaller() // main - test2 := testutils.TestAddress("test2") - // as admin, invite gnouser and test2 - std.TestSetOrigCaller(admin) - users.Invite(caller.String() + "\n" + test2.String()) - // register as caller - std.TestSetOrigCaller(caller) - users.Register(admin, "gnouser", "my profile") - // register as test2 - std.TestSetOrigCaller(test2) - users.Register(admin, "test222", "my profile 2") - println("done") -} - -// Output: -// done diff --git a/examples/gno.land/r/demo/wugnot/wugnot.gno b/examples/gno.land/r/demo/wugnot/wugnot.gno index b72f5161e7d..bcba1435f38 100644 --- a/examples/gno.land/r/demo/wugnot/wugnot.gno +++ b/examples/gno.land/r/demo/wugnot/wugnot.gno @@ -6,9 +6,7 @@ import ( "gno.land/p/demo/grc/grc20" "gno.land/p/demo/ufmt" - pusers "gno.land/p/demo/users" "gno.land/r/demo/grc20reg" - "gno.land/r/demo/users" ) var Token, adm = grc20.NewToken("wrapped GNOT", "wugnot", 0) @@ -23,8 +21,8 @@ func init() { } func Deposit() { - caller := std.PrevRealm().Addr() - sent := std.GetOrigSend() + caller := std.PreviousRealm().Address() + sent := std.OriginSend() amount := sent.AmountOf("ugnot") require(uint64(amount) >= ugnotMinDeposit, ufmt.Sprintf("Deposit below minimum: %d/%d ugnot.", amount, ugnotMinDeposit)) @@ -35,13 +33,13 @@ func Deposit() { func Withdraw(amount uint64) { require(amount >= wugnotMinDeposit, ufmt.Sprintf("Deposit below minimum: %d/%d wugnot.", amount, wugnotMinDeposit)) - caller := std.PrevRealm().Addr() - pkgaddr := std.CurrentRealm().Addr() + caller := std.PreviousRealm().Address() + pkgaddr := std.CurrentRealm().Address() callerBal := Token.BalanceOf(caller) require(amount <= callerBal, ufmt.Sprintf("Insufficient balance: %d available, %d needed.", callerBal, amount)) // send swapped ugnots to qcaller - stdBanker := std.GetBanker(std.BankerTypeRealmSend) + stdBanker := std.NewBanker(std.BankerTypeRealmSend) send := std.Coins{{"ugnot", int64(amount)}} stdBanker.SendCoins(pkgaddr, caller, send) checkErr(adm.Burn(caller, amount)) @@ -65,34 +63,27 @@ func Render(path string) string { func TotalSupply() uint64 { return Token.TotalSupply() } -func BalanceOf(owner pusers.AddressOrName) uint64 { - ownerAddr := users.Resolve(owner) - return Token.BalanceOf(ownerAddr) +func BalanceOf(owner std.Address) uint64 { + return Token.BalanceOf(owner) } -func Allowance(owner, spender pusers.AddressOrName) uint64 { - ownerAddr := users.Resolve(owner) - spenderAddr := users.Resolve(spender) - return Token.Allowance(ownerAddr, spenderAddr) +func Allowance(owner, spender std.Address) uint64 { + return Token.Allowance(owner, spender) } -func Transfer(to pusers.AddressOrName, amount uint64) { - toAddr := users.Resolve(to) +func Transfer(to std.Address, amount uint64) { userTeller := Token.CallerTeller() - checkErr(userTeller.Transfer(toAddr, amount)) + checkErr(userTeller.Transfer(to, amount)) } -func Approve(spender pusers.AddressOrName, amount uint64) { - spenderAddr := users.Resolve(spender) +func Approve(spender std.Address, amount uint64) { userTeller := Token.CallerTeller() - checkErr(userTeller.Approve(spenderAddr, amount)) + checkErr(userTeller.Approve(spender, amount)) } -func TransferFrom(from, to pusers.AddressOrName, amount uint64) { - fromAddr := users.Resolve(from) - toAddr := users.Resolve(to) +func TransferFrom(from, to std.Address, amount uint64) { userTeller := Token.CallerTeller() - checkErr(userTeller.TransferFrom(fromAddr, toAddr, amount)) + checkErr(userTeller.TransferFrom(from, to, amount)) } func require(condition bool, msg string) { diff --git a/examples/gno.land/r/demo/wugnot/z0_filetest.gno b/examples/gno.land/r/demo/wugnot/z0_filetest.gno index 264bc8f19aa..6e9fa0da961 100644 --- a/examples/gno.land/r/demo/wugnot/z0_filetest.gno +++ b/examples/gno.land/r/demo/wugnot/z0_filetest.gno @@ -7,8 +7,6 @@ import ( "gno.land/p/demo/testutils" "gno.land/r/demo/wugnot" - - pusers "gno.land/p/demo/users" ) var ( @@ -18,7 +16,7 @@ var ( ) func main() { - std.TestSetOrigPkgAddr(addrc) + std.TestSetOriginPkgAddress(addrc) std.TestIssueCoins(addrc, std.Coins{{"ugnot", 100000001}}) // TODO: remove this // issue ugnots @@ -29,8 +27,8 @@ func main() { // println(wugnot.Render("queues")) // println("A -", wugnot.Render("")) - std.TestSetOrigCaller(addr1) - std.TestSetOrigSend(std.Coins{{"ugnot", 123_400}}, nil) + std.TestSetOriginCaller(addr1) + std.TestSetOriginSend(std.Coins{{"ugnot", 123_400}}, nil) wugnot.Deposit() printBalances() wugnot.Withdraw(4242) @@ -39,9 +37,9 @@ func main() { func printBalances() { printSingleBalance := func(name string, addr std.Address) { - wugnotBal := wugnot.BalanceOf(pusers.AddressOrName(addr)) - std.TestSetOrigCaller(addr) - robanker := std.GetBanker(std.BankerTypeReadonly) + wugnotBal := wugnot.BalanceOf(addr) + std.TestSetOriginCaller(addr) + robanker := std.NewBanker(std.BankerTypeReadonly) coins := robanker.GetCoins(addr).AmountOf("ugnot") fmt.Printf("| %-13s | addr=%s | wugnot=%-5d | ugnot=%-9d |\n", name, addr, wugnotBal, coins) diff --git a/examples/gno.land/r/docs/avl_pager_with_params/gno.mod b/examples/gno.land/r/docs/avl_pager_with_params/gno.mod new file mode 100644 index 00000000000..aeb5b047762 --- /dev/null +++ b/examples/gno.land/r/docs/avl_pager_with_params/gno.mod @@ -0,0 +1 @@ +module gno.land/r/docs/avl_pager_params diff --git a/examples/gno.land/r/docs/avl_pager_with_params/render.gno b/examples/gno.land/r/docs/avl_pager_with_params/render.gno new file mode 100644 index 00000000000..108f5735b65 --- /dev/null +++ b/examples/gno.land/r/docs/avl_pager_with_params/render.gno @@ -0,0 +1,86 @@ +package avl_pager_params + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/avl/pager" + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/realmpath" +) + +// We'll keep some demo data in an AVL tree to showcase pagination. +var ( + items *avl.Tree + idCounter seqid.ID +) + +func init() { + items = avl.NewTree() + // Populate the tree with 15 sample items for demonstration. + for i := 1; i <= 15; i++ { + id := idCounter.Next().String() + items.Set(id, "Some item value: "+id) + } +} + +func Render(path string) string { + // 1) Parse the incoming path to split route vs. query. + req := realmpath.Parse(path) + // - req.Path contains everything *before* ? or $ (? - query params, $ - gnoweb params) + // - The remaining part (page=2, size=5, etc.) is not in req.Path. + + // 2) If no specific route is provided (req.Path == ""), we’ll show a “home” page + // that displays a list of configs in paginated form. + if req.Path == "" { + return renderHome(path) + } + + // 3) If a route *is* provided (e.g. :SomeKey), + // we will interpret it as a request for a specific page. + return renderConfigItem(req.Path) +} + +// renderHome shows a paginated list of config items if route == "". +func renderHome(fullPath string) string { + // Create a Pager for our config tree, with a default page size of 5. + p := pager.NewPager(items, 5, false) + + // MustGetPageByPath uses the *entire* path (including query parts: ?page=2, etc.) + page := p.MustGetPageByPath(fullPath) + + // Start building the output (plain text or markdown). + out := "# AVL Pager + Render paths\n\n" + out += `This realm showcases how to maintain a paginated list while properly parsing render paths. +You can see how a single page can include a paginated element (like the example below), and how clicking +an item can take you to a dedicated page for that specific item. + +No matter how you browse through the paginated list, the introductory text (this section) remains the same. + +` + + out += ufmt.Sprintf("Showing page %d of %d\n\n", page.PageNumber, page.TotalPages) + + // List items for this page. + for _, item := range page.Items { + // Link each item to a details page: e.g. ":Config01" + out += ufmt.Sprintf("- [Item %s](/r/docs/avl_pager_params:%s)\n", item.Key, item.Key) + } + + // Insert pagination controls (previous/next links, etc.). + out += "\n" + page.Picker() + "\n\n" + out += "### [Go back to r/docs](/r/docs)" + + return out +} + +// renderConfigItem shows details for a single item, e.g. ":item001". +func renderConfigItem(itemName string) string { + value, ok := items.Get(itemName) + if !ok { + return ufmt.Sprintf("**No item found** for key: %s", itemName) + } + + out := ufmt.Sprintf("# Item %s\n\n%s\n\n", itemName, value.(string)) + out += "[Go back](/r/docs/avl_pager_params)" + return out +} diff --git a/examples/gno.land/r/docs/buttons/buttons.gno b/examples/gno.land/r/docs/buttons/buttons.gno index cb050b1bc38..129ffc149e2 100644 --- a/examples/gno.land/r/docs/buttons/buttons.gno +++ b/examples/gno.land/r/docs/buttons/buttons.gno @@ -14,7 +14,7 @@ var ( func UpdateMOTD(newmotd string) { motd = newmotd - lastCaller = std.PrevRealm().Addr() + lastCaller = std.PreviousRealm().Address() } func Render(path string) string { diff --git a/examples/gno.land/r/docs/buttons/buttons_test.gno b/examples/gno.land/r/docs/buttons/buttons_test.gno index 2903fa1a858..c6164f3c687 100644 --- a/examples/gno.land/r/docs/buttons/buttons_test.gno +++ b/examples/gno.land/r/docs/buttons/buttons_test.gno @@ -7,7 +7,7 @@ import ( func TestRenderMotdLink(t *testing.T) { res := Render("motd") - const wantLink = "/r/docs/buttons$help&func=UpdateMOTD&newmotd=Message!" + const wantLink = "/r/docs/buttons$help&func=UpdateMOTD&newmotd=Message%21" if !strings.Contains(res, wantLink) { t.Fatalf("%s\ndoes not contain correct help page link: %s", res, wantLink) } diff --git a/examples/gno.land/r/docs/docs.gno b/examples/gno.land/r/docs/docs.gno index 28bac4171b5..a64660d587a 100644 --- a/examples/gno.land/r/docs/docs.gno +++ b/examples/gno.land/r/docs/docs.gno @@ -1,7 +1,17 @@ package docs +func frontMatter() string { + return `--- +Title: Welcome to the Gno examples documentation index +Description: Welcome to the Gno examples documentation index. Explore various examples to learn more about Gno functionality and usage. +---` +} + func Render(_ string) string { - return `# Gno Examples Documentation + content := "" + content += frontMatter() + content += ` +# Gno Examples Documentation Welcome to the Gno examples documentation index. Explore various examples to learn more about Gno functionality and usage. @@ -13,7 +23,10 @@ Explore various examples to learn more about Gno functionality and usage. - [Source](/r/docs/source) - View realm source code. - [Buttons](/r/docs/buttons) - Add buttons to your realm's render. - [AVL Pager](/r/docs/avl_pager) - Paginate through AVL tree items. +- [AVL Pager + Render paths](/r/docs/avl_pager_params) - Handle render arguments with pagination. - [Img Embed](/r/docs/img_embed) - Demonstrates how to embed an image. +- [Optional Render](/r/docs/optional_render) - Render() is optional in realms. +- [MiniSocial](/r/docs/minisocial) - Minimalistic social media app for learning purposes. - ... @@ -21,4 +34,5 @@ Explore various examples to learn more about Gno functionality and usage. - [Official documentation](https://github.com/gnolang/gno/tree/master/docs) ` + return content } diff --git a/examples/gno.land/r/docs/minisocial/gno.mod b/examples/gno.land/r/docs/minisocial/gno.mod new file mode 100644 index 00000000000..70b47f7a36f --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/gno.mod @@ -0,0 +1 @@ +module gno.land/r/docs/minisocial diff --git a/examples/gno.land/r/docs/minisocial/minisocial.gno b/examples/gno.land/r/docs/minisocial/minisocial.gno new file mode 100644 index 00000000000..ed1c874fea4 --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/minisocial.gno @@ -0,0 +1,18 @@ +package minisocial + +func Render(_ string) string { + return `# MiniSocial +MiniSocial is a minimalistic social media platform made for example purposes. + +There are two versions of this app: +- [V1](/r/docs/minisocial/v1) - handles simple post creation and stores posts in a slice +- [V2](/r/docs/minisocial/v2) - handles post creation, updating, and deletion, +and manages storage more efficiently with an AVL tree. V2 also utilizes different p/ packages to handle pagination, +easier Markdown formatting, etc.` + +} + +// Original work & inspiration here: +// https://gno.land/r/leon/fosdem25/microposts +// https://gno.land/r/moul/microposts +// Find the full tutorial on the official gno.land docs at docs.gno.land diff --git a/examples/gno.land/r/docs/minisocial/v1/admin.gno b/examples/gno.land/r/docs/minisocial/v1/admin.gno new file mode 100644 index 00000000000..05ef8b28df9 --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/v1/admin.gno @@ -0,0 +1,11 @@ +package minisocial + +import "gno.land/p/demo/ownable" + +var Ownable = ownable.NewWithAddress("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5") // @leohhhn + +// ResetPosts allows admin deletion of the posts +func ResetPosts() { + Ownable.AssertCallerIsOwner() + posts = nil +} diff --git a/examples/gno.land/r/docs/minisocial/v1/gno.mod b/examples/gno.land/r/docs/minisocial/v1/gno.mod new file mode 100644 index 00000000000..9e140e688f4 --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/v1/gno.mod @@ -0,0 +1 @@ +module gno.land/r/docs/minisocial/v1 diff --git a/examples/gno.land/r/docs/minisocial/v1/posts.gno b/examples/gno.land/r/docs/minisocial/v1/posts.gno new file mode 100644 index 00000000000..534bbf62c9d --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/v1/posts.gno @@ -0,0 +1,50 @@ +package minisocial + +import ( + "errors" + "std" + "time" + + "gno.land/p/demo/ufmt" +) + +var posts []*Post // inefficient for large amounts of posts; see v2 + +// CreatePost creates a new post +func CreatePost(text string) error { + // If the body of the post is empty, return an error + if text == "" { + return errors.New("empty post text") + } + + // Append the new post to the list + posts = append(posts, &Post{ + text: text, // Set the input text + author: std.PreviousRealm().Address(), // The author of the address is the previous realm, the realm that called this one + createdAt: time.Now(), // Capture the time of the transaction, in this case the block timestamp + }) + + return nil +} + +func Render(_ string) string { + output := "# MiniSocial\n\n" // \n is needed just like in standard Markdown + + // Handle the edge case + if len(posts) == 0 { + output += "No posts.\n" + return output + } + + // Let's append the text of each post to the output + for i, post := range posts { + // Let's append some post metadata + output += ufmt.Sprintf("#### Post #%d\n\n", i) + // Add the stringified post + output += post.String() + // Add a line break for cleaner UI + output += "---\n\n" + } + + return output +} diff --git a/examples/gno.land/r/docs/minisocial/v1/posts_test.gno b/examples/gno.land/r/docs/minisocial/v1/posts_test.gno new file mode 100644 index 00000000000..ac0125772d4 --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/v1/posts_test.gno @@ -0,0 +1,67 @@ +package minisocial + +import ( + "std" + "strings" + "testing" + + "gno.land/p/demo/testutils" // Provides testing utilities +) + +func TestCreatePostSingle(t *testing.T) { + // Get a test address for alice + aliceAddr := testutils.TestAddress("alice") + // TestSetRealm sets the realm caller, in this case Alice + std.TestSetRealm(std.NewUserRealm(aliceAddr)) + + text1 := "Hello World!" + err := CreatePost(text1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + // Get the rendered page + got := Render("") + + // Content should have the text and alice's address in it + if !(strings.Contains(got, text1) && strings.Contains(got, aliceAddr.String())) { + t.Fatal("expected render to contain text & alice's address") + } +} + +func TestCreatePostMultiple(t *testing.T) { + // Initialize a slice to hold the test posts and their authors + posts := []struct { + text string + author string + }{ + {"Hello World!", "alice"}, + {"This is some new text!", "bob"}, + {"Another post by alice", "alice"}, + {"A post by charlie!", "charlie"}, + } + + for _, p := range posts { + // Set the appropriate caller realm based on the author + authorAddr := testutils.TestAddress(p.author) + std.TestSetRealm(std.NewUserRealm(authorAddr)) + + // Create the post + err := CreatePost(p.text) + if err != nil { + t.Fatalf("expected no error for post '%s', got %v", p.text, err) + } + } + + // Get the rendered page + got := Render("") + + // Check that all posts and their authors are present in the rendered output + for _, p := range posts { + expectedText := p.text + expectedAuthor := testutils.TestAddress(p.author).String() // Get the address for the author + if !(strings.Contains(got, expectedText) && strings.Contains(got, expectedAuthor)) { + t.Fatalf("expected render to contain text '%s' and address '%s'", expectedText, expectedAuthor) + } + } +} diff --git a/examples/gno.land/r/docs/minisocial/v1/types.gno b/examples/gno.land/r/docs/minisocial/v1/types.gno new file mode 100644 index 00000000000..7a778854b31 --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/v1/types.gno @@ -0,0 +1,27 @@ +package minisocial + +import ( + "std" // The standard Gno package + "time" // For handling time operations + + "gno.land/p/demo/ufmt" // For string formatting, like `fmt` +) + +// Post defines the main data we keep about each post +type Post struct { + text string // Main text body + author std.Address // Address of the post author, provided by the execution context + createdAt time.Time // When the post was created +} + +// String stringifies a Post +func (p Post) String() string { + out := p.text + out += "\n\n" + out += ufmt.Sprintf("_by %s_, ", p.author) + // We can use `ufmt` to format strings, and the built-in time library formatting function + out += ufmt.Sprintf("_on %s_", p.createdAt.Format("02 Jan 2006, 15:04")) + + out += "\n\n" + return out +} diff --git a/examples/gno.land/r/docs/minisocial/v2/admin.gno b/examples/gno.land/r/docs/minisocial/v2/admin.gno new file mode 100644 index 00000000000..f841ff7fc42 --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/v2/admin.gno @@ -0,0 +1,18 @@ +package minisocial + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/avl/pager" + "gno.land/p/demo/ownable" + "gno.land/p/demo/seqid" +) + +var Ownable = ownable.NewWithAddress("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5") // @leohhhn + +// ResetPosts allows admin deletion of the posts +func ResetPosts() { + Ownable.AssertCallerIsOwner() + posts = avl.NewTree() + postID = seqid.ID(0) + pag = pager.NewPager(posts, 5, true) +} diff --git a/examples/gno.land/r/docs/minisocial/v2/gno.mod b/examples/gno.land/r/docs/minisocial/v2/gno.mod new file mode 100644 index 00000000000..2a7e5a32b67 --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/v2/gno.mod @@ -0,0 +1 @@ +module gno.land/r/docs/minisocial/v2 diff --git a/examples/gno.land/r/docs/minisocial/v2/posts.gno b/examples/gno.land/r/docs/minisocial/v2/posts.gno new file mode 100644 index 00000000000..051bdb9cc51 --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/v2/posts.gno @@ -0,0 +1,141 @@ +package minisocial + +import ( + "errors" + "std" + "strconv" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/avl/pager" + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/md" + + "gno.land/r/sys/users" +) + +var ( + postID seqid.ID // counter for post IDs + posts = avl.NewTree() // seqid.ID.String() > *Post + pag = pager.NewPager(posts, 5, true) // To help with pagination in rendering + + // Errors + ErrEmptyPost = errors.New("empty post text") + ErrPostNotFound = errors.New("post not found") + ErrUpdateWindowExpired = errors.New("update window expired") + ErrUnauthorized = errors.New("you're not authorized to update this post") +) + +// CreatePost creates a new post +func CreatePost(text string) error { + if text == "" { + return ErrEmptyPost + } + + // Get the next ID + // seqid.IDs are sequentially stored in the AVL tree + // This provides chronological order when iterating + id := postID.Next() + + // Set the key:value pair into the AVL tree: + // avl.Tree.Set takes a string for a key, and anything as a value. + // Stringify the key, and set the pointer to a new Post struct + posts.Set(id.String(), &Post{ + id: id, // Set the ID, used later for editing or deletion + text: text, // Set the input text + author: std.PreviousRealm().Address(), // The author of the address is the previous realm, the realm that called this one + createdAt: time.Now(), // Capture the time of the transaction, in this case the block timestamp + updatedAt: time.Now(), + }) + + return nil +} + +// UpdatePost allows the author to update a post +// The post can only be updated up to 10 minutes after posting +func UpdatePost(id string, text string) error { + // Try to get the post + raw, ok := posts.Get(id) + if !ok { + return ErrPostNotFound + } + + // Cast post from AVL tree + post := raw.(*Post) + if std.PreviousRealm().Address() != post.author { + return ErrUnauthorized + } + + // Can only update 10 mins after it was posted + if post.updatedAt.After(post.createdAt.Add(time.Minute * 10)) { + return ErrUpdateWindowExpired + } + + post.text = text + post.updatedAt = time.Now() + + return nil +} + +// DeletePost deletes a post with a specific id +// Only the creator of a post can delete the post +func DeletePost(id string) error { + // Try to get the post + raw, ok := posts.Get(id) + if !ok { + return ErrPostNotFound + } + + // Cast post from AVL tree + post := raw.(*Post) + if std.PreviousRealm().Address() != post.author { + return ErrUnauthorized + } + + // Use avl.Tree.Remove + _, removed := posts.Remove(id) + if !removed { + // This shouldn't happen after all checks above + // If it does, discard any possible state changes + panic("failed to remove post") + } + + return nil +} + +// Render renders the main page of threads +func Render(path string) string { + out := md.H1("MiniSocial") + + if posts.Size() == 0 { + out += "No posts yet!\n\n" + return out + } + + // Get the page from the path + page := pag.MustGetPageByPath(path) + + // Iterate over items in the page + for _, item := range page.Items { + post := item.Value.(*Post) + + // Try resolving the address for a username + text := post.author.String() + user := users.ResolveAddress(post.author) + if user != nil { + text = user.RenderLink("") + } + + out += md.H4(ufmt.Sprintf("Post #%d - %s\n\n", int(post.id), text)) + + out += post.String() + out += md.HorizontalRule() + } + + out += page.Picker() + out += "\n\n" + out += "Page " + strconv.Itoa(page.PageNumber) + " of " + strconv.Itoa(page.TotalPages) + "\n\n" + + return out +} diff --git a/examples/gno.land/r/docs/minisocial/v2/posts_test.gno b/examples/gno.land/r/docs/minisocial/v2/posts_test.gno new file mode 100644 index 00000000000..215056fa138 --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/v2/posts_test.gno @@ -0,0 +1,103 @@ +package minisocial + +import ( + "std" + "strings" + "testing" + + "gno.land/p/demo/testutils" // Provides testing utilities +) + +func TestCreatePostSingle(t *testing.T) { + // Get a test address for alice + aliceAddr := testutils.TestAddress("alice") + // TestSetRealm sets the realm caller, in this case Alice + std.TestSetRealm(std.NewUserRealm(aliceAddr)) + + text1 := "Hello World!" + err := CreatePost(text1) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + // Get the rendered page + got := Render("") + + // Content should have the text and alice's address in it + if !(strings.Contains(got, text1) && strings.Contains(got, aliceAddr.String())) { + t.Fatal("expected render to contain text & alice's address") + } +} + +func TestCreatePostMultiple(t *testing.T) { + // Initialize a slice to hold the test posts and their authors + posts := []struct { + text string + author string + }{ + {"Hello World!", "alice"}, + {"This is some new text!", "bob"}, + {"Another post by alice", "alice"}, + {"A post by charlie!", "charlie"}, + } + + for _, p := range posts { + // Set the appropriate caller realm based on the author + authorAddr := testutils.TestAddress(p.author) + std.TestSetRealm(std.NewUserRealm(authorAddr)) + + // Create the post + err := CreatePost(p.text) + if err != nil { + t.Fatalf("expected no error for post '%s', got %v", p.text, err) + } + } + + // Get the rendered page + got := Render("") + + // Check that all posts and their authors are present in the rendered output + for _, p := range posts { + expectedText := p.text + expectedAuthor := testutils.TestAddress(p.author).String() // Get the address for the author + if !(strings.Contains(got, expectedText) && strings.Contains(got, expectedAuthor)) { + t.Fatalf("expected render to contain text '%s' and address '%s'", expectedText, expectedAuthor) + } + } +} + +func TestReset(t *testing.T) { + aliceAddr := testutils.TestAddress("alice") + std.TestSetRealm(std.NewUserRealm(aliceAddr)) + + text1 := "Hello World!" + _ = CreatePost(text1) + + got := Render("") + if !strings.Contains(got, text1) { + t.Fatal("expected render to contain text1") + } + + // Set admin + std.TestSetRealm(std.NewUserRealm(Ownable.Owner())) + ResetPosts() + + got = Render("") + if strings.Contains(got, text1) { + t.Fatal("expected render to not contain text1") + } + + text2 := "Some other Text!!" + _ = CreatePost(text2) + + got = Render("") + if strings.Contains(got, text1) { + t.Fatal("expected render to not contain text1") + } + + if !strings.Contains(got, text2) { + t.Fatal("expected render to contain text2") + } +} + +// TODO: Add tests for Update & Delete diff --git a/examples/gno.land/r/docs/minisocial/v2/types.gno b/examples/gno.land/r/docs/minisocial/v2/types.gno new file mode 100644 index 00000000000..964bacbad93 --- /dev/null +++ b/examples/gno.land/r/docs/minisocial/v2/types.gno @@ -0,0 +1,36 @@ +package minisocial + +import ( + "std" // The standard Gno package + "time" // For handling time operations + + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" + + "gno.land/r/sys/users" +) + +// Post defines the main data we keep about each post +type Post struct { + id seqid.ID + text string + author std.Address + createdAt time.Time + updatedAt time.Time +} + +func (p Post) String() string { + out := p.text + "\n\n" + + author := p.author.String() + // We can import and use the r/sys/users package to resolve addresses + user := users.ResolveAddress(p.author) + if user != nil { + // RenderLink provides a link that is clickable + // The link goes to the user's profile page + author = user.RenderLink("") + } + + out += ufmt.Sprintf("_by %s on %s_\n\n", author, p.createdAt.Format("02 Jan 2006, 15:04")) + return out +} diff --git a/examples/gno.land/r/docs/optional_render/gno.mod b/examples/gno.land/r/docs/optional_render/gno.mod new file mode 100644 index 00000000000..4c8162ca46d --- /dev/null +++ b/examples/gno.land/r/docs/optional_render/gno.mod @@ -0,0 +1 @@ +module gno.land/r/docs/optional_render diff --git a/examples/gno.land/r/docs/optional_render/optional_render.gno b/examples/gno.land/r/docs/optional_render/optional_render.gno new file mode 100644 index 00000000000..77da30609b3 --- /dev/null +++ b/examples/gno.land/r/docs/optional_render/optional_render.gno @@ -0,0 +1,7 @@ +package optional_render + +func Info() string { + return `Having a Render() function in your realm is optional! +If you do decide to have a Render() function, it must have the following signature: +func Render(path string) string { ... }` +} diff --git a/examples/gno.land/r/gnoland/blog/admin.gno b/examples/gno.land/r/gnoland/blog/admin.gno index 87d465449f3..664b905fba8 100644 --- a/examples/gno.land/r/gnoland/blog/admin.gno +++ b/examples/gno.land/r/gnoland/blog/admin.gno @@ -17,7 +17,7 @@ var ( ) func init() { - // adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis. + // adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis. adminAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul } @@ -43,7 +43,7 @@ func AdminRemoveModerator(addr std.Address) { func NewPostExecutor(slug, title, body, publicationDate, authors, tags string) dao.Executor { callback := func() error { - addPost(std.PrevRealm().Addr(), slug, title, body, publicationDate, authors, tags) + addPost(std.PreviousRealm().Address(), slug, title, body, publicationDate, authors, tags) return nil } @@ -53,7 +53,7 @@ func NewPostExecutor(slug, title, body, publicationDate, authors, tags string) d func ModAddPost(slug, title, body, publicationDate, authors, tags string) { assertIsModerator() - caller := std.GetOrigCaller() + caller := std.OriginCaller() addPost(caller, slug, title, body, publicationDate, authors, tags) } @@ -120,14 +120,14 @@ func isCommenter(addr std.Address) bool { } func assertIsAdmin() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if !isAdmin(caller) { panic("access restricted.") } } func assertIsModerator() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if isAdmin(caller) || isModerator(caller) { return } @@ -135,7 +135,7 @@ func assertIsModerator() { } func assertIsCommenter() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if isAdmin(caller) || isModerator(caller) || isCommenter(caller) { return } diff --git a/examples/gno.land/r/gnoland/blog/gnoblog.gno b/examples/gno.land/r/gnoland/blog/gnoblog.gno index d2a163543e5..ee8fc01db69 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog.gno @@ -15,7 +15,7 @@ func AddComment(postSlug, comment string) { assertIsCommenter() assertNotInPause() - caller := std.GetOrigCaller() + caller := std.OriginCaller() err := b.GetPost(postSlug).AddComment(caller, comment) checkErr(err) } diff --git a/examples/gno.land/r/gnoland/blog/gnoblog_test.gno b/examples/gno.land/r/gnoland/blog/gnoblog_test.gno index b4658db4fb5..a486cf33176 100644 --- a/examples/gno.land/r/gnoland/blog/gnoblog_test.gno +++ b/examples/gno.land/r/gnoland/blog/gnoblog_test.gno @@ -7,9 +7,7 @@ import ( ) func TestPackage(t *testing.T) { - std.TestSetOrigCaller(std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5")) - - author := std.GetOrigCaller() + std.TestSetOriginCaller(std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5")) // by default, no posts. { diff --git a/examples/gno.land/r/gnoland/events/events_test.gno b/examples/gno.land/r/gnoland/events/events_test.gno index 1d79b754ee4..47d327203d7 100644 --- a/examples/gno.land/r/gnoland/events/events_test.gno +++ b/examples/gno.land/r/gnoland/events/events_test.gno @@ -18,7 +18,7 @@ var ( ) func TestAddEvent(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) e1Start := parsedTimeNow.Add(time.Hour * 24 * 5) @@ -61,7 +61,7 @@ func TestAddEvent(t *testing.T) { } func TestAddEventErrors(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) _, err := AddEvent("", "sample desc", "gno.land", "gnome land", "2009-02-13T23:31:31Z", "2009-02-13T23:33:31Z") @@ -85,7 +85,7 @@ func TestAddEventErrors(t *testing.T) { } func TestDeleteEvent(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) e1Start := parsedTimeNow.Add(time.Hour * 24 * 5) @@ -108,7 +108,7 @@ func TestDeleteEvent(t *testing.T) { } func TestEditEvent(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) e1Start := parsedTimeNow.Add(time.Hour * 24 * 5) @@ -138,7 +138,7 @@ func TestEditEvent(t *testing.T) { } func TestInvalidEdit(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) uassert.PanicsWithMessage(t, ErrNoSuchID.Error(), func() { @@ -165,7 +165,7 @@ func TestParseTimes(t *testing.T) { } func TestRenderEventWidget(t *testing.T) { - std.TestSetOrigCaller(su) + std.TestSetOriginCaller(su) std.TestSetRealm(suRealm) // No events yet diff --git a/examples/gno.land/r/gnoland/faucet/admin.gno b/examples/gno.land/r/gnoland/faucet/admin.gno index 37108059b74..d991fcd9816 100644 --- a/examples/gno.land/r/gnoland/faucet/admin.gno +++ b/examples/gno.land/r/gnoland/faucet/admin.gno @@ -79,7 +79,7 @@ func AdminRemoveController(addr std.Address) string { } func assertIsAdmin() error { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if caller != gAdminAddr { return errors.New("restricted for admin") } diff --git a/examples/gno.land/r/gnoland/faucet/faucet.gno b/examples/gno.land/r/gnoland/faucet/faucet.gno index 908b86d4aaf..a173c0423ee 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet.gno @@ -42,8 +42,8 @@ func Transfer(to std.Address, send int64) string { gTotalTransferred = gTotalTransferred.Add(sendCoins) gTotalTransfers++ - banker := std.GetBanker(std.BankerTypeRealmSend) - pkgaddr := std.CurrentRealm().Addr() + banker := std.NewBanker(std.BankerTypeRealmSend) + pkgaddr := std.CurrentRealm().Address() banker.SendCoins(pkgaddr, to, sendCoins) return "" } @@ -53,8 +53,8 @@ func GetPerTransferLimit() int64 { } func Render(_ string) string { - banker := std.GetBanker(std.BankerTypeRealmSend) - balance := banker.GetCoins(std.CurrentRealm().Addr()) + banker := std.NewBanker(std.BankerTypeRealmSend) + balance := banker.GetCoins(std.CurrentRealm().Address()) output := gMessage if gInPause { @@ -65,7 +65,7 @@ func Render(_ string) string { output += ufmt.Sprintf("Balance: %s.\n", balance.String()) output += ufmt.Sprintf("Total transfers: %s (in %d times).\n\n", gTotalTransferred.String(), gTotalTransfers) - output += "Package address: " + std.CurrentRealm().Addr().String() + "\n\n" + output += "Package address: " + std.CurrentRealm().Address().String() + "\n\n" output += ufmt.Sprintf("Admin: %s\n\n ", gAdminAddr.String()) output += ufmt.Sprintf("Controllers:\n\n ") @@ -81,7 +81,7 @@ func Render(_ string) string { } func assertIsController() error { - caller := std.GetOrigCaller() + caller := std.OriginCaller() ok := gControllers.Has(caller.String()) if !ok { diff --git a/examples/gno.land/r/gnoland/faucet/faucet_test.gno b/examples/gno.land/r/gnoland/faucet/faucet_test.gno index cecbb2ebcd6..ca741ab017f 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet_test.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet_test.gno @@ -1,4 +1,4 @@ -package faucet +package faucet_test import ( "std" @@ -33,20 +33,20 @@ func TestPackage(t *testing.T) { // by default, balance is empty, and as a user I cannot call Transfer, or Admin commands. assertBalance(t, test1addr, 0) - std.TestSetOrigCaller(test1addr) + std.TestSetOriginCaller(test1addr) assertErr(t, faucet.Transfer(test1addr, 1000000)) assertErr(t, faucet.AdminAddController(controlleraddr1)) - std.TestSetOrigCaller(controlleraddr1) + std.TestSetOriginCaller(controlleraddr1) assertErr(t, faucet.Transfer(test1addr, 1000000)) // as an admin, add the controller to contract and deposit more 2000gnot to contract - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) assertNoErr(t, faucet.AdminAddController(controlleraddr1)) assertBalance(t, faucetaddr, 1000000000) // now, send some tokens as controller. - std.TestSetOrigCaller(controlleraddr1) + std.TestSetOriginCaller(controlleraddr1) assertNoErr(t, faucet.Transfer(test1addr, 1000000)) assertBalance(t, test1addr, 1000000) assertNoErr(t, faucet.Transfer(test1addr, 1000000)) @@ -55,13 +55,13 @@ func TestPackage(t *testing.T) { // remove controller // as an admin, remove controller - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) assertNoErr(t, faucet.AdminRemoveController(controlleraddr1)) - std.TestSetOrigCaller(controlleraddr1) + std.TestSetOriginCaller(controlleraddr1) assertErr(t, faucet.Transfer(test1addr, 1000000)) // duplicate controller - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) assertNoErr(t, faucet.AdminAddController(controlleraddr1)) assertErr(t, faucet.AdminAddController(controlleraddr1)) // add more than more than allowed controllers @@ -77,13 +77,13 @@ func TestPackage(t *testing.T) { assertErr(t, faucet.AdminAddController(controlleraddr11)) // send more than per transfer limit - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) faucet.AdminSetTransferLimit(300000000) - std.TestSetOrigCaller(controlleraddr1) + std.TestSetOriginCaller(controlleraddr1) assertErr(t, faucet.Transfer(test1addr, 301000000)) // block transefer from the address not on the controllers list. - std.TestSetOrigCaller(controlleraddr11) + std.TestSetOriginCaller(controlleraddr11) assertErr(t, faucet.Transfer(test1addr, 1000000)) } @@ -105,7 +105,7 @@ func assertNoErr(t *testing.T, err string) { func assertBalance(t *testing.T, addr std.Address, expectedBal int64) { t.Helper() - banker := std.GetBanker(std.BankerTypeReadonly) + banker := std.NewBanker(std.BankerTypeReadonly) coins := banker.GetCoins(addr) got := coins.AmountOf("ugnot") diff --git a/examples/gno.land/r/gnoland/faucet/z2_filetest.gno b/examples/gno.land/r/gnoland/faucet/z2_filetest.gno index d0616b3afcd..1490c46ffc9 100644 --- a/examples/gno.land/r/gnoland/faucet/z2_filetest.gno +++ b/examples/gno.land/r/gnoland/faucet/z2_filetest.gno @@ -20,7 +20,7 @@ func main() { controlleraddr1 = testutils.TestAddress("controller1") controlleraddr2 = testutils.TestAddress("controller2") ) - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) err := faucet.AdminAddController(controlleraddr1) if err != "" { panic(err) diff --git a/examples/gno.land/r/gnoland/faucet/z3_filetest.gno b/examples/gno.land/r/gnoland/faucet/z3_filetest.gno index 0da06593710..90612aa3548 100644 --- a/examples/gno.land/r/gnoland/faucet/z3_filetest.gno +++ b/examples/gno.land/r/gnoland/faucet/z3_filetest.gno @@ -22,7 +22,7 @@ func main() { testaddr1 = testutils.TestAddress("test1") testaddr2 = testutils.TestAddress("test2") ) - std.TestSetOrigCaller(adminaddr) + std.TestSetOriginCaller(adminaddr) err := faucet.AdminAddController(controlleraddr1) if err != "" { panic(err) @@ -31,12 +31,12 @@ func main() { if err != "" { panic(err) } - std.TestSetOrigCaller(controlleraddr1) + std.TestSetOriginCaller(controlleraddr1) err = faucet.Transfer(testaddr1, 1000000) if err != "" { panic(err) } - std.TestSetOrigCaller(controlleraddr2) + std.TestSetOriginCaller(controlleraddr2) err = faucet.Transfer(testaddr1, 2000000) if err != "" { panic(err) diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index 3b8f7fcbbe1..debd548e68c 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -16,7 +16,7 @@ const ( ) var ( - ownerAddress = std.GetOrigCaller() + ownerAddress = std.OriginCaller() oracle *gnorkle.Instance postHandler postGnorkleMessageHandler @@ -70,7 +70,7 @@ func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message. // RequestVerification creates a new static feed with a single task that will // instruct an agent to verify the github handle / gno address pair. func RequestVerification(githubHandle string) { - gnoAddress := string(std.GetOrigCaller()) + gnoAddress := string(std.OriginCaller()) if err := oracle.AddFeeds( static.NewSingleValueFeed( gnoAddress, @@ -102,7 +102,7 @@ func GnorkleEntrypoint(message string) string { // SetOwner transfers ownership of the contract to the given address. func SetOwner(owner std.Address) { - if ownerAddress != std.GetOrigCaller() { + if ownerAddress != std.OriginCaller() { panic("only the owner can set a new owner") } @@ -136,7 +136,7 @@ func GetAddressByHandle(handle string) string { func Render(_ string) string { result := "{" var appendComma bool - handleToAddressMap.Iterate("", "", func(handle string, address interface{}) bool { + handleToAddressMap.Iterate("", "", func(handle string, address any) bool { if appendComma { result += "," } diff --git a/examples/gno.land/r/gnoland/ghverify/contract_test.gno b/examples/gno.land/r/gnoland/ghverify/contract_test.gno index 5c0be0afcb1..3bf6e306fed 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract_test.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract_test.gno @@ -8,7 +8,7 @@ import ( ) func TestVerificationLifecycle(t *testing.T) { - defaultAddress := std.GetOrigCaller() + defaultAddress := std.OriginCaller() user1Address := std.Address(testutils.TestAddress("user 1")) user2Address := std.Address(testutils.TestAddress("user 2")) @@ -19,7 +19,7 @@ func TestVerificationLifecycle(t *testing.T) { } // Make a verification request with the created user. - std.TestSetOrigCaller(user1Address) + std.TestSetOriginCaller(user1Address) RequestVerification("deelawn") // A subsequent request from the same address should panic because there is @@ -44,13 +44,13 @@ func TestVerificationLifecycle(t *testing.T) { } // Make a verification request with the created user. - std.TestSetOrigCaller(user2Address) + std.TestSetOriginCaller(user2Address) RequestVerification("omarsy") // Set the caller back to the whitelisted user and verify that the feed data // returned matches what should have been created by the `RequestVerification` // invocation. - std.TestSetOrigCaller(defaultAddress) + std.TestSetOriginCaller(defaultAddress) result = GnorkleEntrypoint("request") expResult := `[{"id":"` + string(user1Address) + `","type":"0","value_type":"string","tasks":[{"gno_address":"` + string(user1Address) + `","github_handle":"deelawn"}]},` + @@ -61,7 +61,7 @@ func TestVerificationLifecycle(t *testing.T) { } // Try to trigger feed ingestion from the non-authorized user. - std.TestSetOrigCaller(user1Address) + std.TestSetOriginCaller(user1Address) func() { defer func() { if r := recover(); r != nil { @@ -75,7 +75,7 @@ func TestVerificationLifecycle(t *testing.T) { } // Set the caller back to the whitelisted user and transfer contract ownership. - std.TestSetOrigCaller(defaultAddress) + std.TestSetOriginCaller(defaultAddress) SetOwner(defaultAddress) // Now trigger the feed ingestion from the user and new owner and only whitelisted address. diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno index 2d1aad8a1a0..00c83258001 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -2,250 +2,51 @@ package home import ( "std" + "strconv" "gno.land/p/demo/ownable" - "gno.land/p/demo/ufmt" "gno.land/p/demo/ui" + "gno.land/p/moul/dynreplacer" blog "gno.land/r/gnoland/blog" - events "gno.land/r/gnoland/events" + "gno.land/r/gnoland/events" "gno.land/r/leon/hof" ) -// XXX: p/demo/ui API is crappy, we need to make it more idiomatic -// XXX: use an updatable block system to update content from a DAO -// XXX: var blocks avl.Tree - var ( override string - admin = ownable.NewWithAddress("g1manfred47kzduec920z88wfr64ylksmdcedlf5") // @moul + Admin = ownable.NewWithAddress("g1manfred47kzduec920z88wfr64ylksmdcedlf5") // @moul ) func Render(_ string) string { - if override != "" { - return override - } - - dom := ui.DOM{Prefix: "r/gnoland/home:"} - dom.Title = "Welcome to gno.land" - dom.Classes = []string{"gno-tmpl-section"} - - // body - dom.Body.Append(introSection()...) - - dom.Body.Append(ui.Jumbotron(discoverLinks())) - - dom.Body.Append( - ui.Columns{3, []ui.Element{ - lastBlogposts(4), - upcomingEvents(), - latestHOFItems(5), - }}, - ) - - dom.Body.Append(ui.HR{}) - dom.Body.Append(playgroundSection()...) - dom.Body.Append(ui.HR{}) - dom.Body.Append(packageStaffPicks()...) - dom.Body.Append(ui.HR{}) - dom.Body.Append(worxDAO()...) - dom.Body.Append(ui.HR{}) - // footer - dom.Footer.Append( - ui.Columns{2, []ui.Element{ - socialLinks(), - quoteOfTheBlock(), - }}, - ) - - // Testnet disclaimer - dom.Footer.Append( - ui.HR{}, - ui.Bold("This is a testnet."), - ui.Text("Package names are not guaranteed to be available for production."), - ) - - return dom.String() -} - -func lastBlogposts(limit int) ui.Element { - posts := blog.RenderLastPostsWidget(limit) - return ui.Element{ - ui.H2("[Latest Blogposts](/r/gnoland/blog)"), - ui.Text(posts), - } -} - -func lastContributions(limit int) ui.Element { - return ui.Element{ - ui.H2("Latest Contributions"), - // TODO: import r/gh to - ui.Link{Text: "View latest contributions", URL: "https://github.com/gnolang/gno/pulls"}, - } -} - -func upcomingEvents() ui.Element { - out, _ := events.RenderEventWidget(events.MaxWidgetSize) - return ui.Element{ - ui.H2("[Latest Events](/r/gnoland/events)"), - ui.Text(out), - } -} - -func latestHOFItems(num int) ui.Element { - submissions := hof.RenderExhibWidget(num) - - return ui.Element{ - ui.H2("[Hall of Fame](/r/leon/hof)"), - ui.Text(submissions), - } -} - -func introSection() ui.Element { - return ui.Element{ - ui.Text("**We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.**"), - ui.Paragraph("With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse."), - ui.Paragraph("Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today."), - } -} - -func worxDAO() ui.Element { - // WorxDAO - // XXX(manfred): please, let me finish a v0, then we can iterate - // highest level == highest responsibility - // teams are responsible for components they don't owne - // flag : realm maintainers VS facilitators - // teams - // committee of trustees to create the directory - // each directory is a name, has a parent and have groups - // homepage team - blocks aggregating events - // XXX: TODO - /*` - # Directory - - * gno.land (owned by group) - * - * gnovm - * gnolang (language) - * gnovm - - current challenges / concerns / issues - * tm2 - * amino - * - - ## Contributors - ``*/ - return ui.Element{ - ui.H2("Contributions (WorxDAO & GoR)"), - // TODO: GoR dashboard + WorxDAO topics - ui.Text(`coming soon`), - } -} - -func quoteOfTheBlock() ui.Element { - quotes := []string{ - "Gno is for Truth.", - "Gno is for Social Coordination.", - "Gno is _not only_ for DeFi.", - "Now, you Gno.", - "Come for the Go, Stay for the Gno.", - } - height := std.GetHeight() - idx := int(height) % len(quotes) - qotb := quotes[idx] - - return ui.Element{ - ui.H2(ufmt.Sprintf("Quote of the ~Day~ Block#%d", height)), - ui.Quote(qotb), - } -} - -func socialLinks() ui.Element { - return ui.Element{ - ui.H2("Socials"), - ui.BulletList{ - // XXX: improve UI to support a nice GO api for such links - ui.Text("Check out our [community projects](https://github.com/gnolang/awesome-gno)"), - ui.Text("[Discord](https://discord.gg/S8nKUqwkPn)"), - ui.Text("[Twitter](https://twitter.com/_gnoland)"), - ui.Text("[Youtube](https://www.youtube.com/@_gnoland)"), - ui.Text("[Telegram](https://t.me/gnoland)"), - }, - } -} - -func playgroundSection() ui.Element { - return ui.Element{ - ui.H2("[Gno Playground](https://play.gno.land)"), - ui.Paragraph(`Gno Playground is a web application designed for building, running, testing, and interacting -with your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code, -execute tests, deploy your realms and packages to gno.land, and explore a multitude of other features.`), - ui.Paragraph("Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land)."), - } -} - -func packageStaffPicks() ui.Element { - // XXX: make it modifiable from a DAO - return ui.Element{ - ui.H2("Explore New Packages and Realms"), - ui.Columns{ - 3, - []ui.Element{ - { - ui.H3("[r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland)"), - ui.BulletList{ - ui.Link{URL: "r/gnoland/blog"}, - ui.Link{URL: "r/gnoland/dao"}, - ui.Link{URL: "r/gnoland/faucet"}, - ui.Link{URL: "r/gnoland/home"}, - ui.Link{URL: "r/gnoland/pages"}, - }, - ui.H3("[r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys)"), - ui.BulletList{ - ui.Link{URL: "r/sys/names"}, - ui.Link{URL: "r/sys/rewards"}, - ui.Link{URL: "/r/sys/validators/v2"}, - }, - }, { - ui.H3("[r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)"), - ui.BulletList{ - ui.Link{URL: "r/demo/boards"}, - ui.Link{URL: "r/demo/users"}, - ui.Link{URL: "r/demo/banktest"}, - ui.Link{URL: "r/demo/foo20"}, - ui.Link{URL: "r/demo/foo721"}, - ui.Link{URL: "r/demo/microblog"}, - ui.Link{URL: "r/demo/nft"}, - ui.Link{URL: "r/demo/types"}, - ui.Link{URL: "r/demo/art/gnoface"}, - ui.Link{URL: "r/demo/art/millipede"}, - ui.Link{URL: "r/demo/groups"}, - ui.Text("..."), - }, - }, { - ui.H3("[p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo)"), - ui.BulletList{ - ui.Link{URL: "p/demo/avl"}, - ui.Link{URL: "p/demo/blog"}, - ui.Link{URL: "p/demo/ui"}, - ui.Link{URL: "p/demo/ufmt"}, - ui.Link{URL: "p/demo/merkle"}, - ui.Link{URL: "p/demo/bf"}, - ui.Link{URL: "p/demo/flow"}, - ui.Link{URL: "p/demo/gnode"}, - ui.Link{URL: "p/demo/grc/grc20"}, - ui.Link{URL: "p/demo/grc/grc721"}, - ui.Text("..."), - }, - }, - }, - }, - } -} - -func discoverLinks() ui.Element { - return ui.Element{ - ui.Text(`
-
+ r := dynreplacer.New() + r.RegisterCallback(":latest-blogposts:", func() string { + return blog.RenderLastPostsWidget(4) + }) + r.RegisterCallback(":upcoming-events:", func() string { + out, _ := events.RenderEventWidget(events.MaxWidgetSize) + return out + }) + r.RegisterCallback(":latest-hof:", func() string { + return hof.RenderExhibWidget(5) + }) + r.RegisterCallback(":qotb:", quoteOfTheBlock) + r.RegisterCallback(":chain-height:", func() string { + return strconv.Itoa(int(std.ChainHeight())) + }) + + template := `# Welcome to gno.land + +We’re building gno.land, set to become the leading open-source smart contract +platform, using Gno, an interpreted and fully deterministic variation of the +Go programming language for succinct and composable smart contracts. + +With transparent and timeless code, gno.land is the next generation of smart +contract platforms, serving as the “GitHub” of the ecosystem, with realms built +using fully transparent, auditable code that anyone can inspect and reuse. + +Intuitive and easy to use, gno.land lowers the barrier to web3 and makes +censorship-resistant platforms accessible to everyone. If you want to help lay +the foundations of a fairer and freer world, join us today. ## Learn about gno.land @@ -258,10 +59,6 @@ func discoverLinks() ui.Element { - [Explore the Ecosystem](/ecosystem) - [Careers](https://jobs.ashbyhq.com/allinbits) -
- -
- ## Build with Gno - [Write Gno in the browser](https://play.gno.land) @@ -271,9 +68,6 @@ func discoverLinks() ui.Element { - [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev) - [Get testnet GNOTs](https://faucet.gno.land) -
-
- ## Explore the universe - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples) @@ -282,17 +76,126 @@ func discoverLinks() ui.Element { - [Testnet 4](https://test4.gno.land/) - [Faucet Hub](https://faucet.gno.land) -
-
`), +## [Latest Blogposts](/r/gnoland/blog) + +:latest-blogposts: + +## [Latest Events](/r/gnoland/events) + +:upcoming-events: + +## [Hall of Fame](/r/leon/hof) + +:latest-hof: + +--- + +## [Gno Playground](https://play.gno.land) + + +Gno Playground is a web application designed for building, running, testing, and +interacting with your Gno code, enhancing your understanding of the Gno +language. With Gno Playground, you can share your code, execute tests, deploy +your realms and packages to gno.land, and explore a multitude of other features. + +Experience the convenience of code sharing and rapid experimentation with +[Gno Playground](https://play.gno.land). + +## Explore New Packages and Realms + +### [r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland) + +- [r/gnoland/blog](/r/gnoland/blog) +- [r/gnoland/blog](/r/gnoland/users) +- [r/gnoland/home](/r/gnoland/home) +- [r/gnoland/pages](/r/gnoland/pages) + +### [r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys) + +- [r/sys/names](/r/sys/names) +- [r/sys/users](/r/sys/users) +- [r/sys/rewards](/r/sys/rewards) +- [/r/sys/validators/v2](/r/sys/validators/v2) + +### [r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo) + +- [r/demo/boards](r/demo/boards) +- [r/demo/banktest](r/demo/banktest) +- [r/demo/foo20](r/demo/foo20) +- [r/demo/foo721](r/demo/foo721) +- [r/demo/microblog](r/demo/microblog) +- [r/demo/nft](r/demo/nft) +- [r/demo/types](r/demo/types) +- [r/demo/art/gnoface](r/demo/art/gnoface) +- [r/demo/art/millipede](r/demo/art/millipede) +- [r/demo/groups](r/demo/groups) +- ... + +### [p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo) + +- [p/demo/avl](p/demo/avl) +- [p/demo/blog](p/demo/blog) +- [p/demo/ui](p/demo/ui) +- [p/demo/ufmt](p/demo/ufmt) +- [p/demo/merkle](p/demo/merkle) +- [p/demo/bf](p/demo/bf) +- [p/demo/flow](p/demo/flow) +- [p/demo/gnode](p/demo/gnode) +- [p/demo/grc/grc20](p/demo/grc/grc20) +- [p/demo/grc/grc721](p/demo/grc/grc721) +- ... + +--- + +## Socials + +- Check out our [community projects](https://github.com/gnolang/awesome-gno) +- [Discord](https://discord.gg/S8nKUqwkPn) +- [Twitter](https://twitter.com/_gnoland) +- [Youtube](https://www.youtube.com/@_gnoland) +- [Telegram](https://t.me/gnoland) + +## Quote of the ~Day~ Block#:chain-height: + +> :qotb: + +--- + +**This is a testnet.** +Package names are not guaranteed to be available for production. +` + + if override != "" { + template = override } + result := r.Replace(template) + return result } -func AdminSetOverride(content string) { - admin.AssertCallerIsOwner() - override = content +func latestHOFItems(num int) ui.Element { + submissions := hof.RenderExhibWidget(num) + + return ui.Element{ + ui.H2("[Hall of Fame](/r/leon/hof)"), + ui.Text(submissions), + } +} + +func quoteOfTheBlock() string { + quotes := []string{ + "Gno is for Truth.", + "Gno is for Social Coordination.", + "Gno is _not only_ for DeFi.", + "Now, you Gno.", + "Come for the Go, Stay for the Gno.", + } + height := std.ChainHeight() + idx := int(height) % len(quotes) + qotb := quotes[idx] + return qotb } -func AdminTransferOwnership(newAdmin std.Address) { - admin.AssertCallerIsOwner() - admin.TransferOwnership(newAdmin) +func AdminSetOverride(content string) { + Admin.AssertCallerIsOwner() + override = content } diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno index 5b5ff5740c3..5ebf3269f81 100644 --- a/examples/gno.land/r/gnoland/home/home_filetest.gno +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -7,21 +7,19 @@ func main() { } // Output: -//
-// // # Welcome to gno.land // -// **We’re building gno.land, set to become the leading open-source smart contract platform, using Gno, an interpreted and fully deterministic variation of the Go programming language for succinct and composable smart contracts.** -// -// With transparent and timeless code, gno.land is the next generation of smart contract platforms, serving as the “GitHub” of the ecosystem, with realms built using fully transparent, auditable code that anyone can inspect and reuse. -// -// -// Intuitive and easy to use, gno.land lowers the barrier to web3 and makes censorship-resistant platforms accessible to everyone. If you want to help lay the foundations of a fairer and freer world, join us today. +// We’re building gno.land, set to become the leading open-source smart contract +// platform, using Gno, an interpreted and fully deterministic variation of the +// Go programming language for succinct and composable smart contracts. // -//
+// With transparent and timeless code, gno.land is the next generation of smart +// contract platforms, serving as the “GitHub” of the ecosystem, with realms built +// using fully transparent, auditable code that anyone can inspect and reuse. // -//
-//
+// Intuitive and easy to use, gno.land lowers the barrier to web3 and makes +// censorship-resistant platforms accessible to everyone. If you want to help lay +// the foundations of a fairer and freer world, join us today. // // ## Learn about gno.land // @@ -34,10 +32,6 @@ func main() { // - [Explore the Ecosystem](/ecosystem) // - [Careers](https://jobs.ashbyhq.com/allinbits) // -//
-// -//
-// // ## Build with Gno // // - [Write Gno in the browser](https://play.gno.land) @@ -47,9 +41,6 @@ func main() { // - [Efficient local development for Gno](https://docs.gno.land/gno-tooling/cli/gno-tooling-gnodev) // - [Get testnet GNOTs](https://faucet.gno.land) // -//
-//
-// // ## Explore the universe // // - [Discover demo packages](https://github.com/gnolang/gno/tree/master/examples) @@ -58,73 +49,50 @@ func main() { // - [Testnet 4](https://test4.gno.land/) // - [Faucet Hub](https://faucet.gno.land) // -//
-//
-//
-// -//
-//
-// // ## [Latest Blogposts](/r/gnoland/blog) // // No posts. -//
-//
// // ## [Latest Events](/r/gnoland/events) // // No events. -//
-//
// // ## [Hall of Fame](/r/leon/hof) // // -//
-//
-// // // --- // // ## [Gno Playground](https://play.gno.land) // // -// Gno Playground is a web application designed for building, running, testing, and interacting -// with your Gno code, enhancing your understanding of the Gno language. With Gno Playground, you can share your code, -// execute tests, deploy your realms and packages to gno.land, and explore a multitude of other features. -// +// Gno Playground is a web application designed for building, running, testing, and +// interacting with your Gno code, enhancing your understanding of the Gno +// language. With Gno Playground, you can share your code, execute tests, deploy +// your realms and packages to gno.land, and explore a multitude of other features. // -// Experience the convenience of code sharing and rapid experimentation with [Gno Playground](https://play.gno.land). -// -// -// --- +// Experience the convenience of code sharing and rapid experimentation with +// [Gno Playground](https://play.gno.land). // // ## Explore New Packages and Realms // -//
-//
-// // ### [r/gnoland](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/gnoland) // -// - [r/gnoland/blog](r/gnoland/blog) -// - [r/gnoland/dao](r/gnoland/dao) -// - [r/gnoland/faucet](r/gnoland/faucet) -// - [r/gnoland/home](r/gnoland/home) -// - [r/gnoland/pages](r/gnoland/pages) +// - [r/gnoland/blog](/r/gnoland/blog) +// - [r/gnoland/blog](/r/gnoland/users) +// - [r/gnoland/home](/r/gnoland/home) +// - [r/gnoland/pages](/r/gnoland/pages) // // ### [r/sys](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/sys) // -// - [r/sys/names](r/sys/names) -// - [r/sys/rewards](r/sys/rewards) +// - [r/sys/names](/r/sys/names) +// - [r/sys/users](/r/sys/users) +// - [r/sys/rewards](/r/sys/rewards) // - [/r/sys/validators/v2](/r/sys/validators/v2) // -//
-//
-// // ### [r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo) // // - [r/demo/boards](r/demo/boards) -// - [r/demo/users](r/demo/users) // - [r/demo/banktest](r/demo/banktest) // - [r/demo/foo20](r/demo/foo20) // - [r/demo/foo721](r/demo/foo721) @@ -136,9 +104,6 @@ func main() { // - [r/demo/groups](r/demo/groups) // - ... // -//
-//
-// // ### [p/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo) // // - [p/demo/avl](p/demo/avl) @@ -153,22 +118,8 @@ func main() { // - [p/demo/grc/grc721](p/demo/grc/grc721) // - ... // -//
-//
-// -// -// --- -// -// ## Contributions (WorxDAO & GoR) -// -// coming soon -// // --- // -// -//
-//
-// // ## Socials // // - Check out our [community projects](https://github.com/gnolang/awesome-gno) @@ -177,20 +128,12 @@ func main() { // - [Youtube](https://www.youtube.com/@_gnoland) // - [Telegram](https://t.me/gnoland) // -//
-//
-// // ## Quote of the ~Day~ Block#123 // // > Now, you Gno. // -//
-//
-// -// // --- // // **This is a testnet.** // Package names are not guaranteed to be available for production. // -//
diff --git a/examples/gno.land/r/gnoland/home/overide_filetest.gno b/examples/gno.land/r/gnoland/home/override_filetest.gno similarity index 64% rename from examples/gno.land/r/gnoland/home/overide_filetest.gno rename to examples/gno.land/r/gnoland/home/override_filetest.gno index be7e33501d6..5d0bae2efcb 100644 --- a/examples/gno.land/r/gnoland/home/overide_filetest.gno +++ b/examples/gno.land/r/gnoland/home/override_filetest.gno @@ -8,10 +8,12 @@ import ( ) func main() { - std.TestSetOrigCaller("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + var admin = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + std.TestSetOriginCaller(admin) home.AdminSetOverride("Hello World!") + println("---") println(home.Render("")) - home.AdminTransferOwnership(testutils.TestAddress("newAdmin")) + home.Admin.TransferOwnership(testutils.TestAddress("newAdmin")) defer func() { r := recover() println("r: ", r) @@ -20,5 +22,6 @@ func main() { } // Output: +// --- // Hello World! // r: ownable: caller is not owner diff --git a/examples/gno.land/r/gnoland/monit/monit.gno b/examples/gno.land/r/gnoland/monit/monit.gno index be94fbdd2bb..7c74e2f5733 100644 --- a/examples/gno.land/r/gnoland/monit/monit.gno +++ b/examples/gno.land/r/gnoland/monit/monit.gno @@ -29,7 +29,7 @@ var ( func Incr() int { counter++ lastUpdate = time.Now() - lastCaller = std.PrevRealm().Addr() + lastCaller = std.PreviousRealm().Address() wd.Alive() return counter } @@ -40,7 +40,7 @@ func Reset() { Ownable.AssertCallerIsOwner() counter = 0 - lastCaller = std.PrevRealm().Addr() + lastCaller = std.PreviousRealm().Address() lastUpdate = time.Now() wd = watchdog.Watchdog{Duration: 5 * time.Minute} } diff --git a/examples/gno.land/r/gnoland/pages/admin.gno b/examples/gno.land/r/gnoland/pages/admin.gno index 71050f4ef57..963c9161df7 100644 --- a/examples/gno.land/r/gnoland/pages/admin.gno +++ b/examples/gno.land/r/gnoland/pages/admin.gno @@ -14,7 +14,7 @@ var ( ) func init() { - // adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis. + // adminAddr = std.OriginCaller() // FIXME: find a way to use this from the main's genesis. adminAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul } @@ -41,7 +41,7 @@ func AdminRemoveModerator(addr std.Address) { func ModAddPost(slug, title, body, publicationDate, authors, tags string) { assertIsModerator() - caller := std.GetOrigCaller() + caller := std.OriginCaller() tagList := strings.Split(tags, ",") authorList := strings.Split(authors, ",") @@ -69,14 +69,14 @@ func isModerator(addr std.Address) bool { } func assertIsAdmin() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if !isAdmin(caller) { panic("access restricted.") } } func assertIsModerator() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if isAdmin(caller) || isModerator(caller) { return } diff --git a/examples/gno.land/r/gnoland/pages/page_contribute.gno b/examples/gno.land/r/gnoland/pages/page_contribute.gno index 0855dc327cd..3448383226e 100644 --- a/examples/gno.land/r/gnoland/pages/page_contribute.gno +++ b/examples/gno.land/r/gnoland/pages/page_contribute.gno @@ -19,9 +19,7 @@ A good place where to start are the issues tagged ["good first issue"](https://g ## Gno Bounties -Additionally, you can look out to help on specific issues labeled as bounties. All contributions will then concur to form your profile for Game of Realms. - -The Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the ["bounty" label](https://github.com/gnolang/gno/labels/bounty). +Additionally, you can look out to help on specific issues labeled as bounties. The Gno bounty program is a good way to find interesting challenges in Gno, and get rewarded for helping us advance the project. We will maintain open and rewardable bounties in the gnolang/gno repository, and you can search all available bounties by using the ["bounty" label](https://github.com/gnolang/gno/labels/bounty). For more detals on the categories and types of bounties, we have a [bounties README](https://github.com/gnolang/bounties). Recommendations on participating in the gno.land Bounty Program: diff --git a/examples/gno.land/r/gnoland/users/gno.mod b/examples/gno.land/r/gnoland/users/gno.mod new file mode 100644 index 00000000000..4dd9f8a5ebf --- /dev/null +++ b/examples/gno.land/r/gnoland/users/gno.mod @@ -0,0 +1 @@ +module gno.land/r/gnoland/users diff --git a/examples/gno.land/r/gnoland/users/users.gno b/examples/gno.land/r/gnoland/users/users.gno new file mode 100644 index 00000000000..5eca5a272e6 --- /dev/null +++ b/examples/gno.land/r/gnoland/users/users.gno @@ -0,0 +1,46 @@ +package users + +import ( + "std" + "strings" + + "gno.land/p/demo/dao" + "gno.land/p/demo/releases" + + "gno.land/r/gov/dao/bridge" +) + +var ( + changelog = releases.NewChangelog("r/gnoland/users") + cd = std.ChainDomain() +) + +const usersPrefix = "gno.land/r/gnoland/users/" + +func init() { + changelog.NewRelease("v1", "/r/gnoland/users/v1", "[Original PR](https://github.com/gnolang/gno/pull/3166)") +} + +func Render(_ string) string { + return changelog.RenderAsTable(10) +} + +func LatestRelease() string { + return cd + changelog.Latest().URL() +} + +// ProposeNewRelease allows a GovDAO proposal to add a release to the changelog +func ProposeNewRelease(newVerPkgPath, note string) dao.Executor { + ver := strings.TrimPrefix(newVerPkgPath, usersPrefix) + if ver == newVerPkgPath || // TrimPrefix returns unchanged newVerPkgPath if !HasPrefix + strings.Contains(ver, "/") { // if has prefix, has to be first child under + panic("r/gnoland/users: invalid version pkgpath") + } + + cb := func() error { + changelog.NewRelease(ver, strings.TrimPrefix(newVerPkgPath, "gno.land"), note) + return nil + } + + return bridge.GovDAO().NewGovDAOExecutor(cb) +} diff --git a/examples/gno.land/r/gnoland/users/v1/admin.gno b/examples/gno.land/r/gnoland/users/v1/admin.gno new file mode 100644 index 00000000000..68937dd1d3e --- /dev/null +++ b/examples/gno.land/r/gnoland/users/v1/admin.gno @@ -0,0 +1,78 @@ +package users + +import ( + "std" + + "gno.land/p/demo/dao" + + "gno.land/r/gov/dao/bridge" + susers "gno.land/r/sys/users" +) + +var paused = false // XXX: replace with p/moul/authz + +// NewSetPausedExecutor allows GovDAO to pause or unpause this realm +func NewSetPausedExecutor(newPausedValue bool) dao.Executor { + cb := func() error { + paused = newPausedValue + return nil + } + + return bridge.GovDAO().NewGovDAOExecutor(cb) +} + +// ProposeNewName allows GovDAO to propose a new name for an existing user +// The associated address and all previous names of a user that changes a name +// are preserved, and all resolve to the new name. +func ProposeNewName(addr std.Address, newName string) dao.Executor { + if matched := reUsername.MatchString(newName); !matched { + panic(ErrInvalidUsername) + } + + userData := susers.ResolveAddress(addr) + if userData == nil { + panic(susers.ErrUserNotExistOrDeleted) + } + + cb := func() error { + if err := userData.UpdateName(newName); err != nil { + return err + } + return nil + } + + return bridge.GovDAO().NewGovDAOExecutor(cb) +} + +// ProposeDeleteUser allows GovDAO to propose deletion of a user +// This will make the associated address and names unresolvable. +// WARN: After deletion, the same address WILL NOT be able to register a new name. +func ProposeDeleteUser(addr std.Address) dao.Executor { + userData := susers.ResolveAddress(addr) + if userData == nil { + panic(susers.ErrUserNotExistOrDeleted) + } + + cb := func() error { + if err := userData.Delete(); err != nil { + return err + } + return nil + } + + return bridge.GovDAO().NewGovDAOExecutor(cb) +} + +// ProposeNewRegisterPrice allows GovDAO to update the price of registration +func ProposeNewRegisterPrice(newPrice int64) dao.Executor { + if newPrice < 0 { + panic("invalid price") + } + + cb := func() error { + registerPrice = newPrice + return nil + } + + return bridge.GovDAO().NewGovDAOExecutor(cb) +} diff --git a/examples/gno.land/r/gnoland/users/v1/errors.gno b/examples/gno.land/r/gnoland/users/v1/errors.gno new file mode 100644 index 00000000000..6acc476b8c5 --- /dev/null +++ b/examples/gno.land/r/gnoland/users/v1/errors.gno @@ -0,0 +1,13 @@ +package users + +import ( + "errors" + + "gno.land/p/demo/ufmt" +) + +var ( + ErrPaused = errors.New("r/gnoland/users: paused") + ErrInvalidPayment = ufmt.Errorf("r/gnoland/users: you need to send exactly %s ugnot", registerPrice) + ErrInvalidUsername = errors.New("r/gnoland/users: invalid username") +) diff --git a/examples/gno.land/r/gnoland/users/v1/gno.mod b/examples/gno.land/r/gnoland/users/v1/gno.mod new file mode 100644 index 00000000000..669e9c66511 --- /dev/null +++ b/examples/gno.land/r/gnoland/users/v1/gno.mod @@ -0,0 +1 @@ +module gno.land/r/gnoland/users/v1 diff --git a/examples/gno.land/r/demo/users/preregister.gno b/examples/gno.land/r/gnoland/users/v1/preregister.gno similarity index 55% rename from examples/gno.land/r/demo/users/preregister.gno rename to examples/gno.land/r/gnoland/users/v1/preregister.gno index e87bb478d4e..baa8ed19b0d 100644 --- a/examples/gno.land/r/demo/users/preregister.gno +++ b/examples/gno.land/r/gnoland/users/v1/preregister.gno @@ -3,14 +3,9 @@ package users import ( "std" - "gno.land/p/demo/users" + susers "gno.land/r/sys/users" ) -// pre-restricted names -var preRestrictedNames = []string{ - "bitcoin", "cosmos", "newtendermint", "ethereum", -} - // pre-registered users var preRegisteredUsers = []struct { Name string @@ -34,36 +29,7 @@ var preRegisteredUsers = []struct { func init() { // add pre-registered users for _, res := range preRegisteredUsers { - // assert not already registered. - _, ok := name2User.Get(res.Name) - if ok { - panic("name already registered") - } - - _, ok = addr2User.Get(res.Address.String()) - if ok { - panic("address already registered") - } - - counter++ - user := &users.User{ - Address: res.Address, - Name: res.Name, - Profile: "", - Number: counter, - Invites: int(0), - Inviter: admin, - } - name2User.Set(res.Name, user) - addr2User.Set(res.Address.String(), user) - } - - // add pre-restricted names - for _, name := range preRestrictedNames { - if _, ok := name2User.Get(name); ok { - panic("name already registered") - } - - restricted.Set(name, true) + // Try registering, skip if it fails + _ = susers.RegisterUser(res.Name, res.Address) } } diff --git a/examples/gno.land/r/gnoland/users/v1/render.gno b/examples/gno.land/r/gnoland/users/v1/render.gno new file mode 100644 index 00000000000..185755a3f97 --- /dev/null +++ b/examples/gno.land/r/gnoland/users/v1/render.gno @@ -0,0 +1,122 @@ +package users + +import ( + "std" + + "gno.land/p/demo/ufmt" + "gno.land/p/moul/md" + "gno.land/p/moul/realmpath" + "gno.land/p/moul/txlink" + + "gno.land/r/demo/profile" + susers "gno.land/r/sys/users" +) + +func Render(path string) string { + req := realmpath.Parse(path) + + if req.Path == "" { + return renderHomePage() + } + + // Otherwise, render the user page + return renderUserPage(req.Path) +} + +func renderHomePage() string { + var out string + + out += "# gno.land user registry\n" + + if paused { + out += md.HorizontalRule() + out += md.H2("This realm is paused.") + out += md.Paragraph("Check out [`gno.land/r/gnoland/users`](/r/gnoland/users) for newer versions of the registry.") + out += md.HorizontalRule() + } + + out += renderIntroParagraph() + + out += md.H2("Latest registrations") + entries := latestUsers.Entries() + if len(entries) == 0 { + out += "No registered users." + } + + for i := len(entries) - 1; i >= 0; i-- { + user := entries[i].(string) + out += ufmt.Sprintf("- User [%s](/r/gnoland/users/v1:%s)\n", md.Bold(user), user) + } + + return out +} + +func renderIntroParagraph() string { + out := md.Paragraph("Welcome to the gno.land user registry (v1). Please register a username.") + out += md.Paragraph(`Registering a username grants the registering address the right to deploy packages and realms +under that username’s namespace. For example, if an address registers the username ` + md.InlineCode("gnome123") + `, it +will gain permission to deploy packages and realms to package paths with the pattern ` + md.InlineCode("gno.land/{p,r}/gnome123/*") + `.`) + + out += md.Paragraph("In V1, usernames must follow these rules, in order to prevent username squatting:") + items := []string{ + "Must start with 3 characters", + "Must end with 3 numbers", + "Have a maximum length of 20 characters", + "With the only special character allowed being `_`", + } + out += md.BulletList(items) + + out += "\n\n" + out += md.Paragraph("In later versions of the registry, vanity usernames will be allowed through specific mechanisms.") + + if !paused { + out += md.H3(ufmt.Sprintf(" [[Click here to register]](%s)", txlink.Call("Register"))) + out += ufmt.Sprintf("Registration price: %f GNOT (%dugnot)\n\n", float64(registerPrice)/1_000_000, registerPrice) + } + + out += md.HorizontalRule() + out += "\n\n" + + return out +} + +// resolveUser resolves the user based on the path, determining if it's a name or address +func resolveUser(path string) (*susers.UserData, bool, bool) { + if std.Address(path).IsValid() { + return susers.ResolveAddress(std.Address(path)), false, false + } + + data, isLatest := susers.ResolveName(path) + return data, isLatest, true +} + +// renderUserPage generates the user page based on user data and path +func renderUserPage(path string) string { + var out string + + // Render single user page + data, isLatest, isName := resolveUser(path) + if data == nil { + out += md.H1("User not found.") + out += "This user does not exist or has been deleted.\n" + return out + } + + out += md.H1("User - " + md.InlineCode(data.Name())) + + if isName && !isLatest { + out += md.Paragraph(ufmt.Sprintf( + "Note: You searched for `%s`, which is a previous name of [`%s`](/r/gnoland/users/v1:%s).", + path, data.Name(), data.Name())) + } else { + out += ufmt.Sprintf("Address: %s\n\n", data.Addr().String()) + + out += md.H2("Bio") + out += profile.GetStringField(data.Addr(), "Bio", "No bio defined.") + out += "\n\n" + out += ufmt.Sprintf("[Update bio](%s)", txlink.Realm("gno.land/r/demo/profile").Call("SetStringField", "field", "Bio")) + out += "\n\n" + } + + return out +} diff --git a/examples/gno.land/r/gnoland/users/v1/users.gno b/examples/gno.land/r/gnoland/users/v1/users.gno new file mode 100644 index 00000000000..6d09935494e --- /dev/null +++ b/examples/gno.land/r/gnoland/users/v1/users.gno @@ -0,0 +1,50 @@ +package users + +import ( + "regexp" + "std" + + "gno.land/p/moul/fifo" + susers "gno.land/r/sys/users" +) + +const ( + reValidUsername = "^[a-z]{3}[_a-z0-9]{0,14}[0-9]{3}$" +) + +var ( + registerPrice = int64(1_000_000) // 1 GNOT + latestUsers = fifo.New(10) // Save the latest 10 users for rendering purposes + reUsername = regexp.MustCompile(reValidUsername) +) + +// Register registers a new username for the caller +// A valid username must start with a minimum of 3 letters, +// end with a minimum of 3 numbers, and be less than 20 chars long. +// All letters must be lowercase, and the only valid special char is `_`. +// Only calls from EOAs are supported. +func Register(username string) error { + std.AssertOriginCall() + + if paused { + return ErrPaused + } + + if std.OriginSend().AmountOf("ugnot") != registerPrice { + return ErrInvalidPayment + } + + if matched := reUsername.MatchString(username); !matched { + return ErrInvalidUsername + } + + registrant := std.PreviousRealm().Address() + if err := susers.RegisterUser(username, registrant); err != nil { + return err + } + + latestUsers.Append(username) + std.Emit("Registeration", "address", registrant.String(), "name", username) + + return nil +} diff --git a/examples/gno.land/r/gnoland/users/v1/users_test.gno b/examples/gno.land/r/gnoland/users/v1/users_test.gno new file mode 100644 index 00000000000..53e2ee808c7 --- /dev/null +++ b/examples/gno.land/r/gnoland/users/v1/users_test.gno @@ -0,0 +1,62 @@ +package users + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" + + susers "gno.land/r/sys/users" +) + +var ( + alice = "alice123" + bob = "bob123" + aliceAddr = testutils.TestAddress(alice) + bobAddr = testutils.TestAddress(bob) +) + +func TestRegister_Valid(t *testing.T) { + std.TestSetOriginSend(std.NewCoins(std.NewCoin("ugnot", 1_000_000)), nil) + std.TestSetRealm(std.NewUserRealm(aliceAddr)) + std.TestSetOriginCaller(aliceAddr) + + uassert.NoError(t, Register(alice)) + res, latest := susers.ResolveName(alice) + + uassert.NotEqual(t, nil, res) + uassert.Equal(t, alice, res.Name()) + uassert.Equal(t, aliceAddr, res.Addr()) + uassert.False(t, res.IsDeleted()) + uassert.True(t, latest) +} + +func TestRegister_Invalid(t *testing.T) { + std.TestSetOriginSend(std.NewCoins(std.NewCoin("ugnot", 1_000_000)), nil) + std.TestSetRealm(std.NewUserRealm(bobAddr)) + std.TestSetOriginCaller(bobAddr) + + // Invalid usernames + uassert.Error(t, Register("alice"), ErrInvalidUsername.Error()) // vanity + uassert.Error(t, Register(""), ErrInvalidUsername.Error()) // empty + uassert.Error(t, Register(" "), ErrInvalidUsername.Error()) // empty + uassert.Error(t, Register("123"), ErrInvalidUsername.Error()) // only numbers + uassert.Error(t, Register("alice&#($)"), ErrInvalidUsername.Error()) // non-allowed chars + uassert.Error(t, Register("Alice123"), ErrInvalidUsername.Error()) // upper-case + uassert.Error(t, Register("toolongusernametoolongusernametoolongusername123"), + ErrInvalidUsername.Error()) // too long + + // Name taken + urequire.NoError(t, Register(bob)) + uassert.Error(t, Register(bob), susers.ErrNameTaken.Error()) +} + +func TestRegister_InvalidPayment(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(bobAddr)) + std.TestSetOriginCaller(bobAddr) + + std.TestSetOriginSend(std.NewCoins(std.NewCoin("ugnot", 12)), nil) // invalid payment amount + uassert.Error(t, Register("alice"), ErrInvalidPayment.Error()) // vanity +} diff --git a/examples/gno.land/r/gnoland/users/v1/z_0_prop1_filetest.gno b/examples/gno.land/r/gnoland/users/v1/z_0_prop1_filetest.gno new file mode 100644 index 00000000000..eb74da59806 --- /dev/null +++ b/examples/gno.land/r/gnoland/users/v1/z_0_prop1_filetest.gno @@ -0,0 +1,264 @@ +package main + +// SEND: 1000000ugnot + +import ( + "std" + + "gno.land/p/demo/dao" + "gno.land/p/demo/testutils" + + users "gno.land/r/gnoland/users/v1" + _ "gno.land/r/gov/dao/init" // so that the govdao.GovDAO initializer is executed + govdao "gno.land/r/gov/dao/v2" + susers "gno.land/r/sys/users" +) + +// Test updating a name via GovDAO + +func init() { + c := std.OriginCaller() + alice := testutils.TestAddress("alice") + + // Register alice + std.TestSetOriginCaller(alice) + std.TestSetRealm(std.NewUserRealm(alice)) + users.Register("alice123") + + // Prop to change name + std.TestSetOriginCaller(c) + std.TestSetRealm(std.NewUserRealm(c)) + ex := users.ProposeNewName(alice, "alice_new123") + + // Create a proposal + prop := dao.ProposalRequest{ + Title: "Change alice's name!", + Description: "", + Executor: ex, + } + + govdao.GovDAO.Propose(prop) +} + +func main() { + println("--") + println(govdao.Render("")) + println("--") + println(govdao.Render("0")) + println("--") + govdao.GovDAO.VoteOnProposal(0, "YES") + println("--") + println(govdao.Render("0")) + println("--") + govdao.GovDAO.ExecuteProposal(0) + println("--") + println(govdao.Render("0")) + + data, _ := susers.ResolveName("alice_new123") + println(data.Addr()) +} + +// Output: +// -- +// # GovDAO Proposals +// +// ## [Prop #0 - Change alice's name!](/r/gov/dao/v2:0) +// +// **Status: ACTIVE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// +// -- +// # Proposal #0 - Change alice's name! +// +// ## Description +// +// No description provided. +// +// ## Proposal information +// +// **Status: ACTIVE** +// +// **Voting stats:** +// - YES 0 (0%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 10 (100%) +// +// +// **Threshold met: FALSE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// #### [[Vote YES](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=YES)] - [[Vote NO](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=NO)] - [[Vote ABSTAIN](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=ABSTAIN)] +// +// +// -- +// -- +// # Proposal #0 - Change alice's name! +// +// ## Description +// +// No description provided. +// +// ## Proposal information +// +// **Status: ACCEPTED** +// +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) +// +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// The voting period for this proposal is over. +// +// +// -- +// -- +// # Proposal #0 - Change alice's name! +// +// ## Description +// +// No description provided. +// +// ## Proposal information +// +// **Status: EXECUTION SUCCESSFUL** +// +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) +// +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// The voting period for this proposal is over. +// +// +// g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh + +// Events: +// [ +// { +// "type": "Registered", +// "attrs": [ +// { +// "key": "name", +// "value": "alice123" +// }, +// { +// "key": "address", +// "value": "g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh" +// } +// ], +// "pkg_path": "gno.land/r/sys/users", +// "func": "RegisterUser" +// }, +// { +// "type": "Registeration", +// "attrs": [ +// { +// "key": "address", +// "value": "g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh" +// }, +// { +// "key": "name", +// "value": "alice123" +// } +// ], +// "pkg_path": "gno.land/r/gnoland/users/v1", +// "func": "Register" +// }, +// { +// "type": "ProposalAdded", +// "attrs": [ +// { +// "key": "proposal-id", +// "value": "0" +// }, +// { +// "key": "proposal-author", +// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" +// } +// ], +// "pkg_path": "gno.land/r/gov/dao/v2", +// "func": "EmitProposalAdded" +// }, +// { +// "type": "VoteAdded", +// "attrs": [ +// { +// "key": "proposal-id", +// "value": "0" +// }, +// { +// "key": "author", +// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" +// }, +// { +// "key": "option", +// "value": "YES" +// } +// ], +// "pkg_path": "gno.land/r/gov/dao/v2", +// "func": "EmitVoteAdded" +// }, +// { +// "type": "ProposalAccepted", +// "attrs": [ +// { +// "key": "proposal-id", +// "value": "0" +// } +// ], +// "pkg_path": "gno.land/r/gov/dao/v2", +// "func": "EmitProposalAccepted" +// }, +// { +// "type": "Updated", +// "attrs": [ +// { +// "key": "alias", +// "value": "alice_new123" +// }, +// { +// "key": "address", +// "value": "g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh" +// } +// ], +// "pkg_path": "gno.land/r/sys/users", +// "func": "UpdateName" +// }, +// { +// "type": "ProposalExecuted", +// "attrs": [ +// { +// "key": "proposal-id", +// "value": "0" +// }, +// { +// "key": "exec-status", +// "value": "accepted" +// } +// ], +// "pkg_path": "gno.land/r/gov/dao/v2", +// "func": "ExecuteProposal" +// } +// ] diff --git a/examples/gno.land/r/gnoland/users/v1/z_1_prop2_filetest.gno b/examples/gno.land/r/gnoland/users/v1/z_1_prop2_filetest.gno new file mode 100644 index 00000000000..ceede7a8a38 --- /dev/null +++ b/examples/gno.land/r/gnoland/users/v1/z_1_prop2_filetest.gno @@ -0,0 +1,262 @@ +package main + +// SEND: 1000000ugnot + +import ( + "std" + + "gno.land/p/demo/dao" + "gno.land/p/demo/testutils" + + users "gno.land/r/gnoland/users/v1" + _ "gno.land/r/gov/dao/init" // so that the govdao.GovDAO initializer is executed + govdao "gno.land/r/gov/dao/v2" + susers "gno.land/r/sys/users" +) + +// Test updating a name via GovDAO + +func init() { + c := std.OriginCaller() + alice := testutils.TestAddress("alice") + + // Register alice + std.TestSetOriginCaller(alice) + std.TestSetRealm(std.NewUserRealm(alice)) + users.Register("alice123") + + // Prop to change name + std.TestSetOriginCaller(c) + std.TestSetRealm(std.NewUserRealm(c)) + ex := users.ProposeDeleteUser(alice) + + // Create a proposal + prop := dao.ProposalRequest{ + Title: "Change alice's name!", + Description: "", + Executor: ex, + } + + govdao.GovDAO.Propose(prop) +} + +func main() { + println("--") + println(govdao.Render("")) + println("--") + println(govdao.Render("0")) + println("--") + govdao.GovDAO.VoteOnProposal(0, "YES") + println("--") + println(govdao.Render("0")) + println("--") + govdao.GovDAO.ExecuteProposal(0) + println("--") + println(govdao.Render("0")) + + data, _ := susers.ResolveName("alice123") + if data == nil { + println("Successfully deleted alice") + } +} + +// Output: +// -- +// # GovDAO Proposals +// +// ## [Prop #0 - Change alice's name!](/r/gov/dao/v2:0) +// +// **Status: ACTIVE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// +// -- +// # Proposal #0 - Change alice's name! +// +// ## Description +// +// No description provided. +// +// ## Proposal information +// +// **Status: ACTIVE** +// +// **Voting stats:** +// - YES 0 (0%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 10 (100%) +// +// +// **Threshold met: FALSE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// #### [[Vote YES](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=YES)] - [[Vote NO](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=NO)] - [[Vote ABSTAIN](/r/gov/dao/v2$help&func=VoteOnProposal&id=0&option=ABSTAIN)] +// +// +// -- +// -- +// # Proposal #0 - Change alice's name! +// +// ## Description +// +// No description provided. +// +// ## Proposal information +// +// **Status: ACCEPTED** +// +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) +// +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// The voting period for this proposal is over. +// +// +// -- +// -- +// # Proposal #0 - Change alice's name! +// +// ## Description +// +// No description provided. +// +// ## Proposal information +// +// **Status: EXECUTION SUCCESSFUL** +// +// **Voting stats:** +// - YES 10 (100%) +// - NO 0 (0%) +// - ABSTAIN 0 (0%) +// - MISSING VOTES 0 (0%) +// +// +// **Threshold met: TRUE** +// +// **Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm** +// +// ### Actions +// +// The voting period for this proposal is over. +// +// +// Successfully deleted alice + +// Events: +// [ +// { +// "type": "Registered", +// "attrs": [ +// { +// "key": "name", +// "value": "alice123" +// }, +// { +// "key": "address", +// "value": "g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh" +// } +// ], +// "pkg_path": "gno.land/r/sys/users", +// "func": "RegisterUser" +// }, +// { +// "type": "Registeration", +// "attrs": [ +// { +// "key": "address", +// "value": "g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh" +// }, +// { +// "key": "name", +// "value": "alice123" +// } +// ], +// "pkg_path": "gno.land/r/gnoland/users/v1", +// "func": "Register" +// }, +// { +// "type": "ProposalAdded", +// "attrs": [ +// { +// "key": "proposal-id", +// "value": "0" +// }, +// { +// "key": "proposal-author", +// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" +// } +// ], +// "pkg_path": "gno.land/r/gov/dao/v2", +// "func": "EmitProposalAdded" +// }, +// { +// "type": "VoteAdded", +// "attrs": [ +// { +// "key": "proposal-id", +// "value": "0" +// }, +// { +// "key": "author", +// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" +// }, +// { +// "key": "option", +// "value": "YES" +// } +// ], +// "pkg_path": "gno.land/r/gov/dao/v2", +// "func": "EmitVoteAdded" +// }, +// { +// "type": "ProposalAccepted", +// "attrs": [ +// { +// "key": "proposal-id", +// "value": "0" +// } +// ], +// "pkg_path": "gno.land/r/gov/dao/v2", +// "func": "EmitProposalAccepted" +// }, +// { +// "type": "Deleted", +// "attrs": [ +// { +// "key": "address", +// "value": "g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh" +// } +// ], +// "pkg_path": "gno.land/r/sys/users", +// "func": "Delete" +// }, +// { +// "type": "ProposalExecuted", +// "attrs": [ +// { +// "key": "proposal-id", +// "value": "0" +// }, +// { +// "key": "exec-status", +// "value": "accepted" +// } +// ], +// "pkg_path": "gno.land/r/gov/dao/v2", +// "func": "ExecuteProposal" +// } +// ] diff --git a/examples/gno.land/r/gnoland/users/z_0_filetest.gno b/examples/gno.land/r/gnoland/users/z_0_filetest.gno new file mode 100644 index 00000000000..00b87338f76 --- /dev/null +++ b/examples/gno.land/r/gnoland/users/z_0_filetest.gno @@ -0,0 +1,38 @@ +package main + +import ( + "gno.land/p/demo/dao" + + "gno.land/r/gnoland/users" + _ "gno.land/r/gov/dao/init" // so that the govdao.GovDAO initializer is executed + govdao "gno.land/r/gov/dao/v2" +) + +func init() { + ex := users.ProposeNewRelease("gno.land/r/gnoland/users/v2", "This is a note!") + + // Create a proposal + prop := dao.ProposalRequest{ + Title: "Propose users registry v2", + Description: "", + Executor: ex, + } + + govdao.GovDAO.Propose(prop) +} + +func main() { + govdao.GovDAO.VoteOnProposal(0, "YES") + govdao.GovDAO.ExecuteProposal(0) + println(users.Render("")) +} + +// Output: +// # r/gnoland/users +// See the r/gnoland/users changelog below. +// +// | Version | Link | Notes | +// | --- | --- | --- | +// | v2 | [r/gnoland/users v2 (latest)](/r/gnoland/users/v2) | This is a note! | +// | v1 | [r/gnoland/users v1](/r/gnoland/users/v1) | [Original PR](https://github.com/gnolang/gno/pull/3166) | +// diff --git a/examples/gno.land/r/gnoland/valopers/v2/valopers.gno b/examples/gno.land/r/gnoland/valopers/v2/valopers.gno index d88ea4b872f..ef4321d1342 100644 --- a/examples/gno.land/r/gnoland/valopers/v2/valopers.gno +++ b/examples/gno.land/r/gnoland/valopers/v2/valopers.gno @@ -90,7 +90,7 @@ func Render(_ string) string { } output := "Valset changes to apply:\n" - valopers.Iterate("", "", func(_ string, value interface{}) bool { + valopers.Iterate("", "", func(_ string, value any) bool { valoper := value.(Valoper) output += valoper.Render() @@ -148,7 +148,7 @@ func GovDAOProposal(address std.Address) { ) // Make sure the valoper is the caller - if std.GetOrigCaller() != address { + if std.OriginCaller() != address { panic(errValoperNotCaller) } diff --git a/examples/gno.land/r/gov/dao/bridge/bridge.gno b/examples/gno.land/r/gov/dao/bridge/bridge.gno index ba47978f33f..73de21c4cfe 100644 --- a/examples/gno.land/r/gov/dao/bridge/bridge.gno +++ b/examples/gno.land/r/gov/dao/bridge/bridge.gno @@ -3,34 +3,60 @@ package bridge import ( "std" + "gno.land/p/demo/dao" "gno.land/p/demo/ownable" ) -const initialOwner = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") // @moul +const ( + initialOwner = std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") // @moul + loader = "gno.land/r/gov/dao/init" +) -var b *Bridge +var ( + b *Bridge + Ownable = ownable.NewWithAddress(initialOwner) +) // Bridge is the active GovDAO // implementation bridge type Bridge struct { - *ownable.Ownable - dao DAO } // init constructs the initial GovDAO implementation func init() { b = &Bridge{ - Ownable: ownable.NewWithAddress(initialOwner), - dao: &govdaoV2{}, + dao: nil, // initially set via r/gov/dao/init + } +} + +// LoadGovDAO loads the initial version of GovDAO into the bridge +// All changes to b.dao need to be done via GovDAO proposals after +func LoadGovDAO(d DAO) { + if std.PreviousRealm().PkgPath() != loader { + panic("unauthorized") } + + b.dao = d } -// SetDAO sets the currently active GovDAO implementation -func SetDAO(dao DAO) { - b.AssertCallerIsOwner() +// NewGovDAOImplChangeExecutor allows creating a GovDAO proposal +// Which will upgrade the GovDAO version inside the bridge +func NewGovDAOImplChangeExecutor(newImpl DAO) dao.Executor { + callback := func() error { + b.dao = newImpl + return nil + } + + return b.dao.NewGovDAOExecutor(callback) +} - b.dao = dao +// SetGovDAO allows the admin to set the GovDAO version manually +// This functionality can be fully disabled by Ownable.DropOwnership(), +// making this realm fully managed by GovDAO. +func SetGovDAO(d DAO) { + Ownable.AssertCallerIsOwner() + b.dao = d } // GovDAO returns the current GovDAO implementation diff --git a/examples/gno.land/r/gov/dao/bridge/bridge_test.gno b/examples/gno.land/r/gov/dao/bridge/bridge_test.gno index 38b5d4be257..ba642141387 100644 --- a/examples/gno.land/r/gov/dao/bridge/bridge_test.gno +++ b/examples/gno.land/r/gov/dao/bridge/bridge_test.gno @@ -1,9 +1,8 @@ package bridge import ( - "testing" - "std" + "testing" "gno.land/p/demo/dao" "gno.land/p/demo/ownable" @@ -27,11 +26,47 @@ func TestBridge_DAO(t *testing.T) { uassert.Equal(t, proposalID, GovDAO().Propose(dao.ProposalRequest{})) } +func TestBridge_LoadGovDAO(t *testing.T) { + t.Run("invalid initializer path", func(t *testing.T) { + std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/init")) // invalid loader + + // Attempt to set a new DAO implementation + uassert.PanicsWithMessage(t, "unauthorized", func() { + LoadGovDAO(&mockDAO{}) + }) + }) + + t.Run("valid loader", func(t *testing.T) { + var ( + initializer = "gno.land/r/gov/dao/init" + proposalID = uint64(10) + mockDAO = &mockDAO{ + proposeFn: func(_ dao.ProposalRequest) uint64 { + return proposalID + }, + } + ) + + std.TestSetRealm(std.NewCodeRealm(initializer)) + + // Attempt to set a new DAO implementation + uassert.NotPanics(t, func() { + LoadGovDAO(mockDAO) + }) + + uassert.Equal( + t, + mockDAO.Propose(dao.ProposalRequest{}), + GovDAO().Propose(dao.ProposalRequest{}), + ) + }) +} + func TestBridge_SetDAO(t *testing.T) { t.Run("invalid owner", func(t *testing.T) { // Attempt to set a new DAO implementation uassert.PanicsWithMessage(t, ownable.ErrUnauthorized.Error(), func() { - SetDAO(&mockDAO{}) + SetGovDAO(&mockDAO{}) }) }) @@ -47,12 +82,12 @@ func TestBridge_SetDAO(t *testing.T) { } ) - std.TestSetOrigCaller(addr) + std.TestSetOriginCaller(addr) - b.Ownable = ownable.NewWithAddress(addr) + Ownable = ownable.NewWithAddress(addr) urequire.NotPanics(t, func() { - SetDAO(mockDAO) + SetGovDAO(mockDAO) }) uassert.Equal( diff --git a/examples/gno.land/r/gov/dao/bridge/v2.gno b/examples/gno.land/r/gov/dao/bridge/v2.gno deleted file mode 100644 index 216419cf31d..00000000000 --- a/examples/gno.land/r/gov/dao/bridge/v2.gno +++ /dev/null @@ -1,42 +0,0 @@ -package bridge - -import ( - "gno.land/p/demo/dao" - "gno.land/p/demo/membstore" - govdao "gno.land/r/gov/dao/v2" -) - -// govdaoV2 is a wrapper for interacting with the /r/gov/dao/v2 Realm -type govdaoV2 struct{} - -func (g *govdaoV2) Propose(request dao.ProposalRequest) uint64 { - return govdao.Propose(request) -} - -func (g *govdaoV2) VoteOnProposal(id uint64, option dao.VoteOption) { - govdao.VoteOnProposal(id, option) -} - -func (g *govdaoV2) ExecuteProposal(id uint64) { - govdao.ExecuteProposal(id) -} - -func (g *govdaoV2) GetPropStore() dao.PropStore { - return govdao.GetPropStore() -} - -func (g *govdaoV2) GetMembStore() membstore.MemberStore { - return govdao.GetMembStore() -} - -func (g *govdaoV2) NewGovDAOExecutor(cb func() error) dao.Executor { - return govdao.NewGovDAOExecutor(cb) -} - -func (g *govdaoV2) NewMemberPropExecutor(cb func() []membstore.Member) dao.Executor { - return govdao.NewMemberPropExecutor(cb) -} - -func (g *govdaoV2) NewMembStoreImplExecutor(cb func() membstore.MemberStore) dao.Executor { - return govdao.NewMembStoreImplExecutor(cb) -} diff --git a/examples/gno.land/r/gov/dao/init/gno.mod b/examples/gno.land/r/gov/dao/init/gno.mod new file mode 100644 index 00000000000..40541f4f152 --- /dev/null +++ b/examples/gno.land/r/gov/dao/init/gno.mod @@ -0,0 +1 @@ +module gno.land/r/gov/dao/init diff --git a/examples/gno.land/r/gov/dao/init/init.gno b/examples/gno.land/r/gov/dao/init/init.gno new file mode 100644 index 00000000000..39bdbedba83 --- /dev/null +++ b/examples/gno.land/r/gov/dao/init/init.gno @@ -0,0 +1,13 @@ +// Package init's only task is to load the initial GovDAO version into the bridge. +// This is done to avoid gov/dao/v2 as a bridge dependency, +// As this can often lead to cyclic dependency errors. +package init + +import ( + "gno.land/r/gov/dao/bridge" + govdao "gno.land/r/gov/dao/v2" +) + +func init() { + bridge.LoadGovDAO(govdao.GovDAO) +} diff --git a/examples/gno.land/r/gov/dao/v2/dao.gno b/examples/gno.land/r/gov/dao/v2/dao.gno index 5ee8e63236a..d69f9901301 100644 --- a/examples/gno.land/r/gov/dao/v2/dao.gno +++ b/examples/gno.land/r/gov/dao/v2/dao.gno @@ -11,8 +11,17 @@ import ( var ( d *simpledao.SimpleDAO // the current active DAO implementation members membstore.MemberStore // the member store + + // GovDAO exposes all functions of this contract as methods + GovDAO = &DAO{} ) +// DAO is an empty struct that allows all +// functions of this realm to be methods instead of functions +// This allows a registry, such as r/gov/dao/bridge +// to take this object and match it to a required interface +type DAO struct{} + const daoPkgPath = "gno.land/r/gov/dao/v2" func init() { @@ -33,7 +42,7 @@ func init() { // Propose is designed to be called by another contract or with // `maketx run`, not by a `maketx call`. -func Propose(request dao.ProposalRequest) uint64 { +func (_ DAO) Propose(request dao.ProposalRequest) uint64 { idx, err := d.Propose(request) if err != nil { panic(err) @@ -43,25 +52,25 @@ func Propose(request dao.ProposalRequest) uint64 { } // VoteOnProposal casts a vote for the given proposal -func VoteOnProposal(id uint64, option dao.VoteOption) { +func (_ DAO) VoteOnProposal(id uint64, option dao.VoteOption) { if err := d.VoteOnProposal(id, option); err != nil { panic(err) } } // ExecuteProposal executes the proposal -func ExecuteProposal(id uint64) { +func (_ DAO) ExecuteProposal(id uint64) { if err := d.ExecuteProposal(id); err != nil { panic(err) } } // GetPropStore returns the active proposal store -func GetPropStore() dao.PropStore { +func (_ DAO) GetPropStore() dao.PropStore { return d } // GetMembStore returns the active member store -func GetMembStore() membstore.MemberStore { +func (_ DAO) GetMembStore() membstore.MemberStore { return members } diff --git a/examples/gno.land/r/gov/dao/v2/poc.gno b/examples/gno.land/r/gov/dao/v2/poc.gno index 30d8a403f6e..81bdc7c9b12 100644 --- a/examples/gno.land/r/gov/dao/v2/poc.gno +++ b/examples/gno.land/r/gov/dao/v2/poc.gno @@ -13,7 +13,7 @@ import ( var errNoChangesProposed = errors.New("no set changes proposed") // NewGovDAOExecutor creates the govdao wrapped callback executor -func NewGovDAOExecutor(cb func() error) dao.Executor { +func (_ DAO) NewGovDAOExecutor(cb func() error) dao.Executor { if cb == nil { panic(errNoChangesProposed) } @@ -25,7 +25,7 @@ func NewGovDAOExecutor(cb func() error) dao.Executor { } // NewMemberPropExecutor returns the GOVDAO member change executor -func NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor { +func (_ DAO) NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor { if changesFn == nil { panic(errNoChangesProposed) } @@ -65,10 +65,10 @@ func NewMemberPropExecutor(changesFn func() []membstore.Member) dao.Executor { return errs } - return NewGovDAOExecutor(callback) + return GovDAO.NewGovDAOExecutor(callback) } -func NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor { +func (_ DAO) NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executor { if changeFn == nil { panic(errNoChangesProposed) } @@ -79,7 +79,7 @@ func NewMembStoreImplExecutor(changeFn func() membstore.MemberStore) dao.Executo return nil } - return NewGovDAOExecutor(callback) + return GovDAO.NewGovDAOExecutor(callback) } // setMembStoreImpl sets a new dao.MembStore implementation diff --git a/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno index 7d8975e1fe8..c8ea983cc73 100644 --- a/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop1_filetest.gno @@ -12,11 +12,13 @@ import ( "gno.land/p/demo/dao" pVals "gno.land/p/sys/validators" + _ "gno.land/r/gov/dao/init" // so that the govdao initializer is executed govdao "gno.land/r/gov/dao/v2" validators "gno.land/r/sys/validators/v2" ) func init() { + changesFn := func() []pVals.Validator { return []pVals.Validator{ { @@ -51,7 +53,7 @@ func init() { Executor: executor, } - govdao.Propose(prop) + govdao.GovDAO.Propose(prop) } func main() { @@ -60,13 +62,13 @@ func main() { println("--") println(govdao.Render("0")) println("--") - govdao.VoteOnProposal(0, dao.YesVote) + govdao.GovDAO.VoteOnProposal(0, dao.YesVote) println("--") println(govdao.Render("0")) println("--") println(validators.Render("")) println("--") - govdao.ExecuteProposal(0) + govdao.GovDAO.ExecuteProposal(0) println("--") println(govdao.Render("0")) println("--") diff --git a/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno index 84a64bc4ee2..f85373a471c 100644 --- a/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop2_filetest.gno @@ -5,6 +5,7 @@ import ( "gno.land/p/demo/dao" gnoblog "gno.land/r/gnoland/blog" + _ "gno.land/r/gov/dao/init" // so that the govdao initializer is executed govdao "gno.land/r/gov/dao/v2" ) @@ -28,7 +29,7 @@ func init() { Executor: ex, } - govdao.Propose(prop) + govdao.GovDAO.Propose(prop) } func main() { @@ -37,13 +38,13 @@ func main() { println("--") println(govdao.Render("0")) println("--") - govdao.VoteOnProposal(0, "YES") + govdao.GovDAO.VoteOnProposal(0, "YES") println("--") println(govdao.Render("0")) println("--") println(gnoblog.Render("")) println("--") - govdao.ExecuteProposal(0) + govdao.GovDAO.ExecuteProposal(0) println("--") println(govdao.Render("0")) println("--") diff --git a/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno index 068f520e7e2..4032ba41d55 100644 --- a/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop3_filetest.gno @@ -6,6 +6,7 @@ import ( "gno.land/p/demo/dao" "gno.land/p/demo/membstore" "gno.land/r/gov/dao/bridge" + _ "gno.land/r/gov/dao/init" // so that the govdao initializer is executed govdao "gno.land/r/gov/dao/v2" ) @@ -34,7 +35,7 @@ func init() { prop := dao.ProposalRequest{ Title: title, Description: description, - Executor: govdao.NewMemberPropExecutor(memberFn), + Executor: govdao.GovDAO.NewMemberPropExecutor(memberFn), } bridge.GovDAO().Propose(prop) @@ -42,25 +43,25 @@ func init() { func main() { println("--") - println(govdao.GetMembStore().Size()) + println(govdao.GovDAO.GetMembStore().Size()) println("--") println(govdao.Render("")) println("--") println(govdao.Render("0")) println("--") - govdao.VoteOnProposal(0, "YES") + govdao.GovDAO.VoteOnProposal(0, "YES") println("--") println(govdao.Render("0")) println("--") println(govdao.Render("")) println("--") - govdao.ExecuteProposal(0) + govdao.GovDAO.ExecuteProposal(0) println("--") println(govdao.Render("0")) println("--") println(govdao.Render("")) println("--") - println(govdao.GetMembStore().Size()) + println(govdao.GovDAO.GetMembStore().Size()) } // Output: diff --git a/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno b/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno index 13ca572c512..72841a7b6d2 100644 --- a/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno +++ b/examples/gno.land/r/gov/dao/v2/prop4_filetest.gno @@ -3,14 +3,15 @@ package main import ( "gno.land/p/demo/dao" "gno.land/r/gov/dao/bridge" + _ "gno.land/r/gov/dao/init" // so that the govdao initializer is executed govdaov2 "gno.land/r/gov/dao/v2" "gno.land/r/sys/params" ) func init() { - mExec := params.NewStringPropExecutor("prop1.string", "value1") - title := "Setting prop1.string param" - comment := "setting prop1.string param" + mExec := params.NewSysParamStringPropExecutor("foo", "bar", "baz", "quz") + title := "Setting foo:bar:baz param" + comment := "new value will be quz" prop := dao.ProposalRequest{ Title: title, Description: comment, @@ -40,7 +41,7 @@ func main() { // -- // # GovDAO Proposals // -// ## [Prop #0 - Setting prop1.string param](/r/gov/dao/v2:0) +// ## [Prop #0 - Setting foo:bar:baz param](/r/gov/dao/v2:0) // // **Status: ACTIVE** // @@ -48,11 +49,11 @@ func main() { // // // -- -// # Proposal #0 - Setting prop1.string param +// # Proposal #0 - Setting foo:bar:baz param // // ## Description // -// setting prop1.string param +// new value will be quz // // ## Proposal information // @@ -76,11 +77,11 @@ func main() { // // -- // -- -// # Proposal #0 - Setting prop1.string param +// # Proposal #0 - Setting foo:bar:baz param // // ## Description // -// setting prop1.string param +// new value will be quz // // ## Proposal information // @@ -104,11 +105,11 @@ func main() { // // -- // -- -// # Proposal #0 - Setting prop1.string param +// # Proposal #0 - Setting foo:bar:baz param // // ## Description // -// setting prop1.string param +// new value will be quz // // ## Proposal information // diff --git a/examples/gno.land/r/gov/dao/v2/render.gno b/examples/gno.land/r/gov/dao/v2/render.gno index 57b7b601523..ea3d7b04019 100644 --- a/examples/gno.land/r/gov/dao/v2/render.gno +++ b/examples/gno.land/r/gov/dao/v2/render.gno @@ -7,7 +7,8 @@ import ( "gno.land/p/demo/dao" "gno.land/p/demo/ufmt" "gno.land/p/moul/txlink" - "gno.land/r/demo/users" + + "gno.land/r/sys/users" ) func Render(path string) string { @@ -41,10 +42,10 @@ func Render(path string) string { out += ufmt.Sprintf("## [Prop #%d - %s](/r/gov/dao/v2:%d)\n\n", propID, title, propID) out += ufmt.Sprintf("**Status: %s**\n\n", strings.ToUpper(prop.Status().String())) - user := users.GetUserByAddress(prop.Author()) + user := users.ResolveAddress(prop.Author()) authorDisplayText := prop.Author().String() if user != nil { - authorDisplayText = ufmt.Sprintf("[%s](/r/demo/users:%s)", user.Name, user.Name) + authorDisplayText = user.RenderLink("") } out += ufmt.Sprintf("**Author: %s**\n\n", authorDisplayText) @@ -91,13 +92,13 @@ func renderAuthor(p dao.Proposal) string { var out string authorUsername := "" - user := users.GetUserByAddress(p.Author()) + user := users.ResolveAddress(p.Author()) if user != nil { - authorUsername = user.Name + authorUsername = user.Name() } if authorUsername != "" { - out += ufmt.Sprintf("**Author: [%s](/r/demo/users:%s)**\n\n", authorUsername, authorUsername) + out += ufmt.Sprintf("**Author: [%s](/r/gnoland/users/v1:%s)**\n\n", authorUsername, authorUsername) } else { out += ufmt.Sprintf("**Author: %s**\n\n", p.Author().String()) } diff --git a/examples/gno.land/r/grepsuzette/home/README.md b/examples/gno.land/r/grepsuzette/home/README.md new file mode 100644 index 00000000000..08095da83f3 --- /dev/null +++ b/examples/gno.land/r/grepsuzette/home/README.md @@ -0,0 +1,2 @@ +Dwilgelindildong, traveler. + diff --git a/examples/gno.land/r/grepsuzette/home/contribs.gno b/examples/gno.land/r/grepsuzette/home/contribs.gno new file mode 100644 index 00000000000..ad1c7e492d7 --- /dev/null +++ b/examples/gno.land/r/grepsuzette/home/contribs.gno @@ -0,0 +1,78 @@ +package home + +func r3() string { + return `# Greps' notable contributions + +My main contributions until the gno.land beta launch are listed below; most aren't in the monorepo, note. + +### Port Joeson from coffeescript to golang + +Worked on this from june 2022 until january 2023. Bounty [applied for](https://github.com/gnolang/bounties-old/issues/33) on Feb 2, 2023. + +Here is the port I did in Go: [grepsuzette/joeson](https://github.com/grepsuzette/joeson/). + + 4. Port JOESON to Go + github.com/jaekwon/joescript + The intent is to create an independent left-recursive PEG parser for Gno. + Optional: port Joescript or Javascript. + 1000 ATOMs from @jaekwon + More GNOTs than from #3. + +There have been many examples posted, including a minimal [LISP REPL](https://github.com/grepsuzette/joeson/tree/master/examples/lisp-repl) and a theorical [study on precedence](https://github.com/grepsuzette/joeson/blob/master/examples/precedence/precedence_test.go) (precedence is often problematic with PEG parsers, this allowed to find a solution, used in the next part). + +### GNO grammar - partial + +In summer 2023, started to port the GNO grammar using Joeson (since there was no news about joeson, so this was an attempt to demonstrate it worked). Grammar was posted in [PR 1156](https://github.com/gnolang/gno/pull/1156). There are only 3 files, they are quite dense: + +1. [joeson_test.go](https://github.com/grepsuzette/gno/blob/joeson/gnovm/pkg/gnolang/joeson_test.go) +1. [joeson_rules.go](https://github.com/grepsuzette/gno/blob/joeson/gnovm/pkg/gnolang/joeson_rules.go) +1. [joeson_f.go](https://github.com/grepsuzette/gno/blob/joeson/gnovm/pkg/gnolang/joeson_f.go) + +### gnAsteroid + +![asteroid](https://raw.githubusercontent.com/grepsuzette/gfx/master/asteroid160.png) + +**gnAsteroid** is an asteroid creation-kit, it was started around the time the joeson port was published, but didn't have a name back then. + +Asteroids orbit gno.land, it's the same blockchain, but different frontend, +themable, working with wiki-like markdown files (enabling realms from gno.land +to be rendered there). + +* [asteroid 0](https://gnAsteroid.com) - asteroid explaining what it is, containing instructions, to use, deploy on [Akash](https://gnasteroid.com/publishing/akash.md), [Vercel](https://gnasteroid.com/publishing/vercel.md). +* [greps' asteroid](https://greps.gnAsteroid.com) +* [gnAsteroid](https://github.com/gnAsteroid/gnAsteroid) - The github for gnAsteroid. + +### Research with markdown and gnoweb, mini-games, experiments (summer-oct 2024) + +A series of experiments with gnoweb 1.0 lead from the summer 2024, to try to advocate for keeping html +and css enabled in gnoweb, or at least to try to determine what we could +potentially miss without. Gnoweb1.0, markdown, html, css, js-less. + +Note those still work with [gnAsteroid](https://gnAsteroid.com), or with gnoweb +running with the -web-html switch. As of now they are rendered through an +asteroid. + +| 1 | 2 | +| :-------------------: | :-------------------------: | +| ![parrot](https://raw.githubusercontent.com/grepsuzette/gfx/master/parrot160.png) | ![octopus](https://raw.githubusercontent.com/grepsuzette/gfx/master/octopus160.png) | +| [tic-tac-toe](https://greps.gnAsteroid.com/r/grepsuzette/pr2554/v6/games/tictactoe) | [minesweeper](https://greps.gnAsteroid.com/r/grepsuzette/pr2554/v6/games/minesweeper) | + +Check the [other experiments here](/conjects/gnoweb.md). + +![octopus](https://raw.githubusercontent.com/grepsuzette/gfx/master/screen-minesweeper390.png) + +### Tendermint vuln retrospective (2023) + +Also worked on an anthology of publicly knowned vulnerabilities that affected Tendermint. + +* [Cosmos-sdk vulnerability retrospective](https://github.com/gnolang/gno/issues/587) +* found most vulns were not affecting our Tendermint version, however: +* [demonstrated vulnerability to BSC 2022-10-07 hack](https://github.com/gnolang/gno/pull/583) +* [proposed fix to vuln to BSC 2022-10-07 hack (merged)](https://github.com/gnolang/gno/pull/584) +* not all of them were tested, as I was hoping some more feedback before to continue. + +There is also a small [GNO mail](https://github.com/gnolang/gno/pull/641) which got no UI is discussed in [one of my articles](https://greps.gnasteroid.com/articles/encryptedmail.md). + +Thanks for reading! +` +} diff --git a/examples/gno.land/r/grepsuzette/home/gno.mod b/examples/gno.land/r/grepsuzette/home/gno.mod new file mode 100644 index 00000000000..0406abd5091 --- /dev/null +++ b/examples/gno.land/r/grepsuzette/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/grepsuzette/home diff --git a/examples/gno.land/r/grepsuzette/home/render.gno b/examples/gno.land/r/grepsuzette/home/render.gno new file mode 100644 index 00000000000..13a6ed7aced --- /dev/null +++ b/examples/gno.land/r/grepsuzette/home/render.gno @@ -0,0 +1,62 @@ +package home + +func Render(path string) string { + switch path { + case "3": + return r3() + case "2": + return r2() + default: + return r1() + } +} + +const tripleBackquote = "```" +const art = ` + ( ) + ( ) + ) ) + ( ( /\ + (_) / \ /\ + ________[_]________ /\/ \/ \ + /\ /\ ______ \ / /\/\ /\/\ + / \ //_\ \ /\ \ /\/\/ \/ \ + /\ / /\/\ //___\ \__/ \ \/ + ' + / \ /\/ \//_____\ \ |[]| \ . t . + /\/\/\/ //_______\ \|__| \ p e +/ \ /XXXXXXXXXX\ \ o l + \ /_I_II I__I_\__________________\ + r e + I_I| I__I_____[]_|_[]_____I . t + I_II I__I_____[]_|_[]_____I + ' + I II__I I XXXXXXX I + ~~~~~" "~~~~~~~~~~~~~~~~~~~~~~~~ :*:*:*:*:* +` + +func r1() string { + return "# greps' (gn)home" + + ` +You've reached the terrestrial realms of Grepsuzette on gno.land. + +` + tripleBackquote + art + tripleBackquote + ` + +I am often on my [GNO asteroid](https://greps.gnAsteroid.com) too. + +* Public address: g1fjh9y7ausp27dqsdq0qrcsnmgvwm6829v2au7d +* Contributor since summer 2022 ([notable contributions](/r/grepsuzette/home:3)) +* You can try my games in GNO (they use gnoweb -html): + * [tic-tac-toe](https://greps.gnasteroid.com/r/grepsuzette/pr2554/v6/games/tictactoe) + * [minesweeper](https://greps.gnasteroid.com/r/grepsuzette/pr2554/v6/games/minesweeper) +` +} + +func r2() string { + return `A manual index, until there's an automated way: + +* [home](home/): greps' home on gno.land +* [games](games/): series of games + +I'm often on my [GNO asteroid][1] too. + +[1]: https://greps.gnAsteroid.com +` +} diff --git a/examples/gno.land/r/jjoptimist/home/config.gno b/examples/gno.land/r/jjoptimist/home/config.gno new file mode 100644 index 00000000000..7f6ad955806 --- /dev/null +++ b/examples/gno.land/r/jjoptimist/home/config.gno @@ -0,0 +1,32 @@ +package home + +import ( + "std" + + "gno.land/p/demo/ownable" +) + +type Config struct { + Title string + Description string + Github string +} + +var config = Config{ + Title: "JJOptimist's Home Realm 🏠", + Description: "Exploring Gno and building on-chain", + Github: "jjoptimist", +} + +var Ownable = ownable.NewWithAddress(std.Address("g16vfw3r7zuz43fhky3xfsuc2hdv9tnhvlkyn0nj")) + +func GetConfig() Config { + return config +} + +func UpdateConfig(newTitle, newDescription, newGithub string) { + Ownable.AssertCallerIsOwner() + config.Title = newTitle + config.Description = newDescription + config.Github = newGithub +} diff --git a/examples/gno.land/r/jjoptimist/home/gno.mod b/examples/gno.land/r/jjoptimist/home/gno.mod new file mode 100644 index 00000000000..b4b591f6ab7 --- /dev/null +++ b/examples/gno.land/r/jjoptimist/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/jjoptimist/home diff --git a/examples/gno.land/r/jjoptimist/home/home.gno b/examples/gno.land/r/jjoptimist/home/home.gno new file mode 100644 index 00000000000..e97898c41de --- /dev/null +++ b/examples/gno.land/r/jjoptimist/home/home.gno @@ -0,0 +1,82 @@ +package home + +import ( + "std" + "strconv" + "time" + + "gno.land/r/leon/hof" +) + +const ( + gnomeArt1 = ` /\ + / \ + ,,,,, +(o.o) +(\_/) +-"-"-` + + gnomeArt2 = ` /\ + / \ + ,,,,, +(^.^) +(\_/) + -"-` + + gnomeArt3 = ` /\ + / \ + ,,,,, +(*.*) +(\_/) +"-"-"` + + gnomeArt4 = ` /\ + / \ + ,,,,, +(o.~) +(\_/) + -"-` +) + +var creation time.Time + +func getGnomeArt(height int64) string { + var art string + switch { + case height%7 == 0: + art = gnomeArt4 // winking gnome + case height%5 == 0: + art = gnomeArt3 // starry-eyed gnome + case height%3 == 0: + art = gnomeArt2 // happy gnome + default: + art = gnomeArt1 // regular gnome + } + return "```\n" + art + "\n```\n" +} + +func init() { + creation = time.Now() + hof.Register() +} + +func Render(path string) string { + height := std.ChainHeight() + + output := "# " + config.Title + "\n\n" + + output += "## About Me\n" + output += "- 👋 Hi, I'm JJOptimist\n" + output += getGnomeArt(height) + output += "- 🌱 " + config.Description + "\n" + + output += "## Contact\n" + output += "- 📫 GitHub: [" + config.Github + "](https://github.com/" + config.Github + ")\n" + + output += "\n---\n" + output += "_Realm created: " + creation.Format("2006-01-02 15:04:05 UTC") + "_\n" + output += "_Owner: " + Ownable.Owner().String() + "_\n" + output += "_Current Block Height: " + strconv.Itoa(int(height)) + "_" + + return output +} diff --git a/examples/gno.land/r/jjoptimist/home/home_test.gno b/examples/gno.land/r/jjoptimist/home/home_test.gno new file mode 100644 index 00000000000..742204cca71 --- /dev/null +++ b/examples/gno.land/r/jjoptimist/home/home_test.gno @@ -0,0 +1,60 @@ +package home + +import ( + "strings" + "testing" +) + +func TestConfig(t *testing.T) { + cfg := GetConfig() + + if cfg.Title != "JJOptimist's Home Realm 🏠" { + t.Errorf("Expected title to be 'JJOptimist's Home Realm 🏠', got %s", cfg.Title) + } + if cfg.Description != "Exploring Gno and building on-chain" { + t.Errorf("Expected description to be 'Exploring Gno and building on-chain', got %s", cfg.Description) + } + if cfg.Github != "jjoptimist" { + t.Errorf("Expected github to be 'jjoptimist', got %s", cfg.Github) + } +} + +func TestRender(t *testing.T) { + output := Render("") + + // Test that required sections are present + if !strings.Contains(output, "# "+config.Title) { + t.Error("Rendered output missing title") + } + if !strings.Contains(output, "## About Me") { + t.Error("Rendered output missing About Me section") + } + if !strings.Contains(output, "## Contact") { + t.Error("Rendered output missing Contact section") + } + if !strings.Contains(output, config.Description) { + t.Error("Rendered output missing description") + } + if !strings.Contains(output, config.Github) { + t.Error("Rendered output missing github link") + } +} + +func TestGetGnomeArt(t *testing.T) { + tests := []struct { + height int64 + expected string + }{ + {7, gnomeArt4}, // height divisible by 7 + {5, gnomeArt3}, // height divisible by 5 + {3, gnomeArt2}, // height divisible by 3 + {2, gnomeArt1}, // default case + } + + for _, tt := range tests { + art := getGnomeArt(tt.height) + if !strings.Contains(art, tt.expected) { + t.Errorf("For height %d, expected art containing %s, got %s", tt.height, tt.expected, art) + } + } +} diff --git a/examples/gno.land/r/leon/config/config.gno b/examples/gno.land/r/leon/config/config.gno index bc800ec8263..222ca98ebfa 100644 --- a/examples/gno.land/r/leon/config/config.gno +++ b/examples/gno.land/r/leon/config/config.gno @@ -3,61 +3,83 @@ package config import ( "errors" "std" + "strings" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ownable" + "gno.land/p/demo/seqid" ) var ( - main std.Address // leon's main address - backup std.Address // backup address + cfgID seqid.ID + configs = avl.NewTree() + + absPath = strings.TrimPrefix(std.CurrentRealm().PkgPath(), std.ChainDomain()) + + // SafeObjects + OwnableMain = ownable.NewWithAddress("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5") + OwnableBackup = ownable.NewWithAddress("g1lavlav7zwsjqlzzl3qdl3nl242qtf638vnhdjh") - ErrInvalidAddr = errors.New("leon's config: invalid address") ErrUnauthorized = errors.New("leon's config: unauthorized") ) -func init() { - main = "g125em6arxsnj49vx35f0n0z34putv5ty3376fg5" +type Config struct { + id seqid.ID + name string + lines string + updated time.Time } -func Address() std.Address { - return main -} +func AddConfig(name, lines string) { + if !IsAuthorized(std.PreviousRealm().Address()) { + panic(ErrUnauthorized) + } -func Backup() std.Address { - return backup + id := cfgID.Next() + configs.Set(id.String(), Config{ + id: id, + name: name, + lines: lines, + updated: time.Now(), + }) } -func SetAddress(a std.Address) error { - if !a.IsValid() { - return ErrInvalidAddr +func EditConfig(id string, name, lines string) { + if !IsAuthorized(std.PreviousRealm().Address()) { + panic(ErrUnauthorized) } - if err := checkAuthorized(); err != nil { - return err + raw, ok := configs.Remove(id) + if !ok { + panic("no config with that id") } - main = a - return nil + conf := raw.(Config) + // Overwrites data + conf.lines = lines + conf.name = name + conf.updated = time.Now() } -func SetBackup(a std.Address) error { - if !a.IsValid() { - return ErrInvalidAddr +func RemoveConfig(id string) { + if !IsAuthorized(std.PreviousRealm().Address()) { + panic(ErrUnauthorized) } - if err := checkAuthorized(); err != nil { - return err + if _, ok := configs.Remove(id); !ok { + panic("no config with that id") } - - backup = a - return nil } -func checkAuthorized() error { - caller := std.PrevRealm().Addr() - isAuthorized := caller == main || caller == backup - - if !isAuthorized { - return ErrUnauthorized +func UpdateBanner(newBanner string) { + if !IsAuthorized(std.PreviousRealm().Address()) { + panic(ErrUnauthorized) } - return nil + banner = newBanner +} + +func IsAuthorized(addr std.Address) bool { + return addr == OwnableMain.Owner() || addr == OwnableBackup.Owner() } diff --git a/examples/gno.land/r/leon/config/render.gno b/examples/gno.land/r/leon/config/render.gno new file mode 100644 index 00000000000..e32435b0d59 --- /dev/null +++ b/examples/gno.land/r/leon/config/render.gno @@ -0,0 +1,69 @@ +package config + +import ( + "strconv" + + p "gno.land/p/demo/avl/pager" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/md" + "gno.land/p/moul/realmpath" + "gno.land/p/moul/txlink" +) + +var ( + banner = "---\n[[Leon's Home page]](/r/leon/home) | [[Leon's snippets]](/r/leon/config) | [[GitHub: @leohhhn]](https://github.com/leohhhn)\n\n---" + pager = p.NewPager(configs, 10, true) +) + +func Banner() string { + return banner +} + +func Render(path string) (out string) { + req := realmpath.Parse(path) + if req.Path == "" { + out += md.H1("Leon's configs & snippets") + + out += ufmt.Sprintf("Leon's main address: %s\n\n", OwnableMain.Owner().String()) + out += ufmt.Sprintf("Leon's backup address: %s\n\n", OwnableBackup.Owner().String()) + + out += md.H2("Snippets") + + if configs.Size() == 0 { + out += "No configs yet :c\n\n" + } else { + page := pager.MustGetPageByPath(path) + for _, item := range page.Items { + out += ufmt.Sprintf("- [%s](%s:%s)\n\n", item.Value.(Config).name, absPath, item.Key) + } + + out += page.Picker() + out += "\n\n" + out += "Page " + strconv.Itoa(page.PageNumber) + " of " + strconv.Itoa(page.TotalPages) + "\n\n" + } + + out += Banner() + + return out + } + + return renderConfPage(req.Path) +} + +func renderConfPage(id string) (out string) { + raw, ok := configs.Get(id) + if !ok { + out += md.H1("404") + out += "That config does not exist :/" + return out + } + + conf := raw.(Config) + out += md.H1(conf.name) + out += ufmt.Sprintf("```\n%s\n```\n\n", conf.lines) + out += ufmt.Sprintf("_Last updated on %s_\n\n", conf.updated.Format("02 Jan, 2006")) + out += md.HorizontalRule() + out += ufmt.Sprintf("[[EDIT]](%s) - [[DELETE]](%s)", txlink.Call("EditConfig", "id", conf.id.String()), txlink.Call("RemoveConfig", "id", conf.id.String())) + + return out +} diff --git a/examples/gno.land/r/leon/hof/datasource_test.gno b/examples/gno.land/r/leon/hof/datasource_test.gno index 376f981875f..2cbabb08ddb 100644 --- a/examples/gno.land/r/leon/hof/datasource_test.gno +++ b/examples/gno.land/r/leon/hof/datasource_test.gno @@ -151,7 +151,7 @@ func TestItemRecord(t *testing.T) { content, _ := r.Content() wantContent := "# Submission #1\n\n\n```\ngno.land/r/demo/test\n```\n\nby demo\n\n" + "[View realm](/r/demo/test)\n\nSubmitted at Block #42\n\n" + - "#### [2👍](/r/leon/hof$help&func=Upvote&pkgpath=gno.land/r/demo/test) - " + - "[1👎](/r/leon/hof$help&func=Downvote&pkgpath=gno.land/r/demo/test)\n\n" + "**[2👍](/r/leon/hof$help&func=Upvote&pkgpath=gno.land%2Fr%2Fdemo%2Ftest) - " + + "[1👎](/r/leon/hof$help&func=Downvote&pkgpath=gno.land%2Fr%2Fdemo%2Ftest)**\n\n" uassert.Equal(t, wantContent, content) } diff --git a/examples/gno.land/r/leon/hof/errors.gno b/examples/gno.land/r/leon/hof/errors.gno index 7277f65fa76..5fe51c922cd 100644 --- a/examples/gno.land/r/leon/hof/errors.gno +++ b/examples/gno.land/r/leon/hof/errors.gno @@ -1,11 +1,11 @@ package hof import ( - "errors" + "gno.land/p/leon/pkgerr" ) var ( - ErrNoSuchItem = errors.New("hof: no such item exists") - ErrDoubleUpvote = errors.New("hof: cannot upvote twice") - ErrDoubleDownvote = errors.New("hof: cannot downvote twice") + ErrNoSuchItem = pkgerr.New("no such item exists") + ErrDoubleUpvote = pkgerr.New("cannot upvote twice") + ErrDoubleDownvote = pkgerr.New("cannot downvote twice") ) diff --git a/examples/gno.land/r/leon/hof/hof.gno b/examples/gno.land/r/leon/hof/hof.gno index 147a0dd1a95..ef20d02d7a7 100644 --- a/examples/gno.land/r/leon/hof/hof.gno +++ b/examples/gno.land/r/leon/hof/hof.gno @@ -10,6 +10,8 @@ import ( "gno.land/p/demo/ownable" "gno.land/p/demo/pausable" "gno.land/p/demo/seqid" + + "gno.land/r/leon/config" ) var ( @@ -24,7 +26,7 @@ type ( Exhibition struct { itemCounter seqid.ID description string - items *avl.Tree // pkgPath > Item + items *avl.Tree // pkgPath > &Item itemsSorted *avl.Tree // same data but sorted, storing pointers } @@ -43,7 +45,7 @@ func init() { itemsSorted: avl.NewTree(), } - Ownable = ownable.NewWithAddress(std.Address("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5")) + Ownable = ownable.NewWithAddress(config.OwnableMain.Owner()) // OrigSendOwnable? Pausable = pausable.NewFromOwnable(Ownable) } @@ -54,7 +56,7 @@ func Register() { return } - submission := std.PrevRealm() + submission := std.PreviousRealm() pkgpath := submission.PkgPath() // Must be called from code @@ -71,7 +73,7 @@ func Register() { i := &Item{ id: id, pkgpath: pkgpath, - blockNum: std.GetHeight(), + blockNum: std.ChainHeight(), upvote: avl.NewTree(), downvote: avl.NewTree(), } @@ -85,14 +87,14 @@ func Register() { func Upvote(pkgpath string) { rawItem, ok := exhibition.items.Get(pkgpath) if !ok { - panic(ErrNoSuchItem.Error()) + panic(ErrNoSuchItem) } item := rawItem.(*Item) - caller := std.PrevRealm().Addr().String() + caller := std.PreviousRealm().Address().String() if item.upvote.Has(caller) { - panic(ErrDoubleUpvote.Error()) + panic(ErrDoubleUpvote) } item.upvote.Set(caller, struct{}{}) @@ -101,14 +103,14 @@ func Upvote(pkgpath string) { func Downvote(pkgpath string) { rawItem, ok := exhibition.items.Get(pkgpath) if !ok { - panic(ErrNoSuchItem.Error()) + panic(ErrNoSuchItem) } item := rawItem.(*Item) - caller := std.PrevRealm().Addr().String() + caller := std.PreviousRealm().Address().String() if item.downvote.Has(caller) { - panic(ErrDoubleDownvote.Error()) + panic(ErrDoubleDownvote) } item.downvote.Set(caller, struct{}{}) @@ -116,19 +118,19 @@ func Downvote(pkgpath string) { func Delete(pkgpath string) { if !Ownable.CallerIsOwner() { - panic(ownable.ErrUnauthorized.Error()) + panic(ownable.ErrUnauthorized) } i, ok := exhibition.items.Get(pkgpath) if !ok { - panic(ErrNoSuchItem.Error()) + panic(ErrNoSuchItem) } if _, removed := exhibition.itemsSorted.Remove(i.(*Item).id.String()); !removed { - panic(ErrNoSuchItem.Error()) + panic(ErrNoSuchItem) } if _, removed := exhibition.items.Remove(pkgpath); !removed { - panic(ErrNoSuchItem.Error()) + panic(ErrNoSuchItem) } } diff --git a/examples/gno.land/r/leon/hof/hof_test.gno b/examples/gno.land/r/leon/hof/hof_test.gno index 4d6f70eab88..4e133a6bbe8 100644 --- a/examples/gno.land/r/leon/hof/hof_test.gno +++ b/examples/gno.land/r/leon/hof/hof_test.gno @@ -103,7 +103,7 @@ func TestDownvote(t *testing.T) { func TestDelete(t *testing.T) { userRealm := std.NewUserRealm(admin) std.TestSetRealm(userRealm) - std.TestSetOrigCaller(admin) + std.TestSetOriginCaller(admin) uassert.PanicsWithMessage(t, ErrNoSuchItem.Error(), func() { Delete("nonexistentpkgpath") diff --git a/examples/gno.land/r/leon/hof/render.gno b/examples/gno.land/r/leon/hof/render.gno index 868262bedc7..66d966a8760 100644 --- a/examples/gno.land/r/leon/hof/render.gno +++ b/examples/gno.land/r/leon/hof/render.gno @@ -38,11 +38,9 @@ func (e Exhibition) Render(path string, dashboard bool) string { out += "
\n\n" - page := pager.NewPager(e.itemsSorted, pageSize, false).MustGetPageByPath(path) - - for i := len(page.Items) - 1; i >= 0; i-- { - item := page.Items[i] + page := pager.NewPager(e.itemsSorted, pageSize, true).MustGetPageByPath(path) + for _, item := range page.Items { out += "
\n\n" id, _ := seqid.FromString(item.Key) out += ufmt.Sprintf("### Submission #%d\n\n", int(id)) @@ -63,7 +61,7 @@ func (i Item) Render(dashboard bool) string { out += ufmt.Sprintf("[View realm](%s)\n\n", strings.TrimPrefix(i.pkgpath, "gno.land")) // gno.land/r/leon/home > /r/leon/home out += ufmt.Sprintf("Submitted at Block #%d\n\n", i.blockNum) - out += ufmt.Sprintf("#### [%d👍](%s) - [%d👎](%s)\n\n", + out += ufmt.Sprintf("**[%d👍](%s) - [%d👎](%s)**\n\n", i.upvote.Size(), txlink.Call("Upvote", "pkgpath", i.pkgpath), i.downvote.Size(), txlink.Call("Downvote", "pkgpath", i.pkgpath), ) @@ -100,7 +98,7 @@ func RenderExhibWidget(itemsToRender int) string { out := "" i := 0 - exhibition.items.Iterate("", "", func(key string, value interface{}) bool { + exhibition.items.Iterate("", "", func(key string, value any) bool { item := value.(*Item) out += ufmt.Sprintf("- %s\n", fqname.RenderLink(item.pkgpath, "")) diff --git a/examples/gno.land/r/leon/home/home.gno b/examples/gno.land/r/leon/home/home.gno index cf33260cc6b..6f7fca01c68 100644 --- a/examples/gno.land/r/leon/home/home.gno +++ b/examples/gno.land/r/leon/home/home.gno @@ -19,7 +19,24 @@ var ( abtMe [2]string ) +func Render(path string) string { + out := "# Leon's Homepage\n\n" + + out += renderAboutMe() + out += renderBlogPosts() + out += "\n\n" + out += renderArt() + out += "\n\n" + out += config.Banner() + out += "\n\n" + + return out +} + func init() { + hof.Register() + mirror.Register(std.CurrentRealm().PkgPath(), Render) + pfp = "https://i.imgflip.com/91vskx.jpg" pfpCaption = "[My favourite painting & pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)" abtMe = [2]string{ @@ -30,16 +47,12 @@ life-long learner, and sharer of knowledge.`, My contributions to gno.land can mainly be found [here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn). -TODO import r/gh -`, +TODO import r/gh`, } - - hof.Register() - mirror.Register(std.CurrentRealm().PkgPath(), Render) } func UpdatePFP(url, caption string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !config.IsAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -48,7 +61,7 @@ func UpdatePFP(url, caption string) { } func UpdateAboutMe(col1, col2 string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !config.IsAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -56,20 +69,9 @@ func UpdateAboutMe(col1, col2 string) { abtMe[1] = col2 } -func Render(path string) string { - out := "# Leon's Homepage\n\n" - - out += renderAboutMe() - out += renderBlogPosts() - out += "\n\n" - out += renderArt() - - return out -} - func renderBlogPosts() string { out := "" - //out += "## Leon's Blog Posts" + // out += "## Leon's Blog Posts" // todo fetch blog posts authored by @leohhhn // and render them @@ -116,7 +118,7 @@ func renderArt() string { func renderGnoFace() string { out := "
\n\n" - out += gnoface.Render(strconv.Itoa(int(std.GetHeight()))) + out += gnoface.Render(strconv.Itoa(int(std.ChainHeight()))) out += "
\n\n" return out @@ -125,12 +127,8 @@ func renderGnoFace() string { func renderMillipede() string { out := "
\n\n" out += "Millipede\n\n" - out += "```\n" + millipede.Draw(int(std.GetHeight())%10+1) + "```\n" + out += "```\n" + millipede.Draw(int(std.ChainHeight())%10+1) + "```\n" out += "
\n\n" return out } - -func isAuthorized(addr std.Address) bool { - return addr == config.Address() || addr == config.Backup() -} diff --git a/examples/gno.land/r/mason/home/gno.mod b/examples/gno.land/r/mason/home/gno.mod new file mode 100644 index 00000000000..0b934f1a5d1 --- /dev/null +++ b/examples/gno.land/r/mason/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/mason/home diff --git a/examples/gno.land/r/mason/home/home.gno b/examples/gno.land/r/mason/home/home.gno new file mode 100644 index 00000000000..557227cf5d4 --- /dev/null +++ b/examples/gno.land/r/mason/home/home.gno @@ -0,0 +1,77 @@ +package home + +import ( + "gno.land/p/mason/md" +) + +const ( + gnomeArt1 = ` /\ + / \ + ,,,,, + (o.o) + (\_/) + -"-"-` + dgnonut = ` + #$$$$$$$$* + #$$@@@@@@@@@@$$$# + #$$@@@@@@@@@@@@@$$$$# + #$$$@@@@$$$$$$$$$$$$$$$$* + #$$$$$$$$$$$$$$$$$$$$$$$$$#! + #$$$$$$$$$############$$$$$##* + !##$$$$$$####**********#####$###* + =##$$$$$###****!!!!!!!!!***######*! + *##$$$###***!!!!!!==!!!!!!**######*= + !*#######***!!!=;;;;;====!!!!**####** + !*#######**!!!==;;::::::;;==!!!**###**! + !*######***!==;::~~~~~~:::;;=!!!***#***= + =**#####**!!==;::~-,,,,,--~:;;=!!!******! + !**####***!==;:~-,.. ..,,-~:;==!!******!; + ;!**###***!!=;:~-,. ..-~:;==!!*****!= + =!*******!!==::-. .,-::==!!*****!= + =!*******!!=;:~, .-~:;=!!!****!=: + ~=!*******!==;:-. .,-:;=!!!****!=; + :=!*******!==;~,. ,-:;==!!!***!=; + :=!******!!==:~, ,-:;=!!!***!!=; + :=!!*****!!=;:~, ,~:;=!!****!!=;- + :=!!!****!!==;~, -~;==!!****!!=;- + :;=!!*****!!=;:- -:;=!!*****!!=:- + ~;=!!!****!!==;~ :;=!!*****!!!;:- + ~;==!!****!!!==: ;=!!******!!=;:, + ~:==!!!****!!!=;~ :=!********!!=;:. + -:;==!!*****!!!!; =!*********!==;: + ,~;==!!*******!!== =**#####****!==:~ + ,~:;=!!!!*********! **#######***!!=;~- + -~;;=!!!!**********! *##$$$$$###***!!=:~. + ,~:;==!!!****##########$$$$$$$$###****!=;:~ + -~:;==!!!***####$$$$$$@@@@@$$$###**!!=;:~, + ,-~:;=!!!***####$$$$@@@@@@$$$$##**!!!=;:-. + -~:;;=!!!***###$$$$$@@@@$$$$##***!!=;:-. + .-~:;;=!!!***###$$$$$$$$$$$##***!==;:~- + .-~:;==!!!!**####$$$$$$$###**!!==;:~- + ,-~::;==!!!!***########****!!==;:~-. + ,-~:;;==!!!!!***********!!!==;:~,. + ,,~~::;====!!!!!!!!!!!!!==;::~,. + .,-~::;;;===!!!!!!!!===;::~-,. + ,--~~:;;;;========;;::~--. + .,,-~~:::::::::::~~~-,,. + ..,---~~~~~~~~~--,.. + ..,,,,,,,,,... + ...` +) + +func Render(path string) string { + home := md.New() + home.H1("Mason's Realm") + + home.Im("https://cdn.esawebb.org/archives/images/screen/weic2428a.jpg", "Placeholder") + home.P("Welcome to my realm. " + md.Link("github", "https://github.com/masonmcbride")) + + home.H3("Dgnonut") + home.Code(dgnonut) + + home.H3("More") + home.Code(gnomeArt1) + home.Bullet("Credit to " + md.Link("JJOptimist", "https://gno.land/r/jjoptimist/home") + " for this gnome art.") + home.Bullet("I'm testing out my markdown system.") + return home.Render() +} diff --git a/examples/gno.land/r/matijamarjanovic/home/config.gno b/examples/gno.land/r/matijamarjanovic/home/config.gno index 2a9669c0b58..8a5a4135025 100644 --- a/examples/gno.land/r/matijamarjanovic/home/config.gno +++ b/examples/gno.land/r/matijamarjanovic/home/config.gno @@ -48,7 +48,7 @@ func SetBackup(newAddress std.Address) error { } func checkAuthorized() error { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if caller != mainAddr && caller != backupAddr { return errorUnauthorized } @@ -57,7 +57,7 @@ func checkAuthorized() error { } func AssertAuthorized() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() if caller != mainAddr && caller != backupAddr { panic(errorUnauthorized) } diff --git a/examples/gno.land/r/matijamarjanovic/home/home.gno b/examples/gno.land/r/matijamarjanovic/home/home.gno index 3757324108a..133a5857f2b 100644 --- a/examples/gno.land/r/matijamarjanovic/home/home.gno +++ b/examples/gno.land/r/matijamarjanovic/home/home.gno @@ -71,21 +71,21 @@ func maxOfThree(a, b, c int64) int64 { } func VoteModern() { - ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + ugnotAmount := std.OriginSend().AmountOf("ugnot") votes := ugnotAmount modernVotes += votes updateCurrentTheme() } func VoteClassic() { - ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + ugnotAmount := std.OriginSend().AmountOf("ugnot") votes := ugnotAmount classicVotes += votes updateCurrentTheme() } func VoteMinimal() { - ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + ugnotAmount := std.OriginSend().AmountOf("ugnot") votes := ugnotAmount minimalVotes += votes updateCurrentTheme() @@ -106,10 +106,10 @@ func updateCurrentTheme() { func CollectBalance() { AssertAuthorized() - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) ownerAddr := Address() - banker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr())) + banker.SendCoins(std.CurrentRealm().Address(), ownerAddr, banker.GetCoins(std.CurrentRealm().Address())) } func Render(path string) string { diff --git a/examples/gno.land/r/matijamarjanovic/home/home_test.gno b/examples/gno.land/r/matijamarjanovic/home/home_test.gno index 8cc6e6e5608..10e2e6db6fc 100644 --- a/examples/gno.land/r/matijamarjanovic/home/home_test.gno +++ b/examples/gno.land/r/matijamarjanovic/home/home_test.gno @@ -11,7 +11,7 @@ import ( // Helper function to set up test environment func setupTest() { - std.TestSetOrigCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) + std.TestSetOriginCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) } func TestUpdatePFP(t *testing.T) { @@ -41,7 +41,7 @@ func TestVoteModern(t *testing.T) { coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) - std.TestSetOrigSend(coinsSent, coinsSpent) + std.TestSetOriginSend(coinsSent, coinsSpent) VoteModern() uassert.Equal(t, int64(75000000), modernVotes, "Modern votes should be calculated correctly") @@ -55,7 +55,7 @@ func TestVoteClassic(t *testing.T) { coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) - std.TestSetOrigSend(coinsSent, coinsSpent) + std.TestSetOriginSend(coinsSent, coinsSpent) VoteClassic() uassert.Equal(t, int64(75000000), classicVotes, "Classic votes should be calculated correctly") @@ -69,7 +69,7 @@ func TestVoteMinimal(t *testing.T) { coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) - std.TestSetOrigSend(coinsSent, coinsSpent) + std.TestSetOriginSend(coinsSent, coinsSpent) VoteMinimal() uassert.Equal(t, int64(75000000), minimalVotes, "Minimal votes should be calculated correctly") diff --git a/examples/gno.land/r/morgan/guestbook/guestbook.gno b/examples/gno.land/r/morgan/guestbook/guestbook.gno index be9e9db6133..1a17d8df6b4 100644 --- a/examples/gno.land/r/morgan/guestbook/guestbook.gno +++ b/examples/gno.land/r/morgan/guestbook/guestbook.gno @@ -43,22 +43,22 @@ const ( // Sign signs the guestbook, with the specified message. func Sign(message string) { - prev := std.PrevRealm() + prev := std.PreviousRealm() switch { case !prev.IsUser(): panic(errNotAUser) - case hasSigned.Has(prev.Addr().String()): + case hasSigned.Has(prev.Address().String()): panic(errAlreadySigned) } message = validateMessage(message) guestbook.Set(signatureID.Next().Binary(), Signature{ Message: message, - Author: prev.Addr(), + Author: prev.Address(), // NOTE: time.Now() will yield the "block time", which is deterministic. Time: time.Now(), }) - hasSigned.Set(prev.Addr().String(), struct{}{}) + hasSigned.Set(prev.Address().String(), struct{}{}) } func validateMessage(msg string) string { @@ -99,7 +99,7 @@ func Render(maxID string) string { var lastID seqid.ID var printed int - guestbook.ReverseIterate("", maxIDBinary, func(key string, val interface{}) bool { + guestbook.ReverseIterate("", maxIDBinary, func(key string, val any) bool { sig := val.(Signature) message := strings.ReplaceAll(sig.Message, "\n", "\n> ") bld.WriteString("> " + message + "\n>\n") diff --git a/examples/gno.land/r/morgan/guestbook/guestbook_test.gno b/examples/gno.land/r/morgan/guestbook/guestbook_test.gno index b14fee45b42..a68aacb2e7c 100644 --- a/examples/gno.land/r/morgan/guestbook/guestbook_test.gno +++ b/examples/gno.land/r/morgan/guestbook/guestbook_test.gno @@ -33,7 +33,7 @@ func TestSign(t *testing.T) { } func TestSign_FromRealm(t *testing.T) { - std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) + std.TestSetRealm(std.NewCodeRealm("gno.land/r/gnoland/users/v1")) defer func() { rec := recover() diff --git a/examples/gno.land/r/moul/config/config.gno b/examples/gno.land/r/moul/config/config.gno index a4f24411747..0302163c3c8 100644 --- a/examples/gno.land/r/moul/config/config.gno +++ b/examples/gno.land/r/moul/config/config.gno @@ -14,7 +14,7 @@ func UpdateAddr(newAddr std.Address) { } func AssertIsAdmin() { - if std.GetOrigCaller() != addr { + if std.OriginCaller() != addr { panic("restricted area") } } diff --git a/examples/gno.land/r/moul/home/z2_filetest.gno b/examples/gno.land/r/moul/home/z2_filetest.gno index f471280d8ef..4ce99f21dfa 100644 --- a/examples/gno.land/r/moul/home/z2_filetest.gno +++ b/examples/gno.land/r/moul/home/z2_filetest.gno @@ -7,7 +7,7 @@ import ( ) func main() { - std.TestSetOrigCaller("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + std.TestSetOriginCaller("g1manfred47kzduec920z88wfr64ylksmdcedlf5") home.AddTodo("aaa") home.AddTodo("bbb") home.AddTodo("ccc") @@ -62,10 +62,10 @@ func main() { // | Key | Value | // | --- | --- | // | `std.CurrentRealm().PkgPath()` | gno.land/r/moul/home | -// | `std.CurrentRealm().Addr()` | g1h8h57ntxadcze3f703skymfzdwa6t3ugf0nq3z | -// | `std.PrevRealm().PkgPath()` | | -// | `std.PrevRealm().Addr()` | g1manfred47kzduec920z88wfr64ylksmdcedlf5 | -// | `std.GetHeight()` | 123 | +// | `std.CurrentRealm().Address()` | g1h8h57ntxadcze3f703skymfzdwa6t3ugf0nq3z | +// | `std.PreviousRealm().PkgPath()` | | +// | `std.PreviousRealm().Address()` | g1manfred47kzduec920z88wfr64ylksmdcedlf5 | +// | `std.ChainHeight()` | 123 | // | `time.Now().Format(time.RFC3339)` | 2009-02-13T23:31:30Z | // // diff --git a/examples/gno.land/r/moul/microposts/README.md b/examples/gno.land/r/moul/microposts/README.md new file mode 100644 index 00000000000..5c7763020cd --- /dev/null +++ b/examples/gno.land/r/moul/microposts/README.md @@ -0,0 +1,5 @@ +# fork of `leon/fosdem25/microposts` + +removing optional lines to make the code more concise for slides. + +Original work here: https://gno.land/r/leon/fosdem25/microposts diff --git a/examples/gno.land/r/moul/microposts/gno.mod b/examples/gno.land/r/moul/microposts/gno.mod new file mode 100644 index 00000000000..00386f6e856 --- /dev/null +++ b/examples/gno.land/r/moul/microposts/gno.mod @@ -0,0 +1 @@ +module gno.land/r/moul/microposts diff --git a/examples/gno.land/r/moul/microposts/microposts_test.gno b/examples/gno.land/r/moul/microposts/microposts_test.gno new file mode 100644 index 00000000000..61929081e34 --- /dev/null +++ b/examples/gno.land/r/moul/microposts/microposts_test.gno @@ -0,0 +1,3 @@ +package microposts + +// empty file just to make sure that `gno test` tries to parse the implementation. diff --git a/examples/gno.land/r/moul/microposts/post.gno b/examples/gno.land/r/moul/microposts/post.gno new file mode 100644 index 00000000000..0832d8ac3c6 --- /dev/null +++ b/examples/gno.land/r/moul/microposts/post.gno @@ -0,0 +1,18 @@ +package microposts + +import ( + "std" + "time" +) + +type Post struct { + text string + author std.Address + createdAt time.Time +} + +func (p Post) String() string { + out := p.text + "\n" + out += "_" + p.createdAt.Format("02 Jan 2006, 15:04") + ", by " + p.author.String() + "_" + return out +} diff --git a/examples/gno.land/r/moul/microposts/realm.gno b/examples/gno.land/r/moul/microposts/realm.gno new file mode 100644 index 00000000000..85155faf69a --- /dev/null +++ b/examples/gno.land/r/moul/microposts/realm.gno @@ -0,0 +1,25 @@ +package microposts + +import ( + "std" + "strconv" + "time" +) + +var posts []*Post + +func CreatePost(text string) { + posts = append(posts, &Post{ + text: text, + author: std.PreviousRealm().Address(), // provided by env + createdAt: time.Now(), + }) +} + +func Render(_ string) string { + out := "# Posts\n" + for i := len(posts) - 1; i >= 0; i-- { + out += "### Post " + strconv.Itoa(i) + "\n" + posts[i].String() + } + return out +} diff --git a/examples/gno.land/r/moul/present/admin.gno b/examples/gno.land/r/moul/present/admin.gno deleted file mode 100644 index ab99b1725c5..00000000000 --- a/examples/gno.land/r/moul/present/admin.gno +++ /dev/null @@ -1,96 +0,0 @@ -package present - -import ( - "std" - "strings" - - "gno.land/p/demo/avl" -) - -var ( - adminAddr std.Address - moderatorList avl.Tree - inPause bool -) - -func init() { - // adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis. - adminAddr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" -} - -func AdminSetAdminAddr(addr std.Address) { - assertIsAdmin() - adminAddr = addr -} - -func AdminSetInPause(state bool) { - assertIsAdmin() - inPause = state -} - -func AdminAddModerator(addr std.Address) { - assertIsAdmin() - moderatorList.Set(addr.String(), true) -} - -func AdminRemoveModerator(addr std.Address) { - assertIsAdmin() - moderatorList.Set(addr.String(), false) // XXX: delete instead? -} - -func ModAddPost(slug, title, body, publicationDate, authors, tags string) { - assertIsModerator() - - caller := std.GetOrigCaller() - tagList := strings.Split(tags, ",") - authorList := strings.Split(authors, ",") - - err := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList) - checkErr(err) -} - -func ModEditPost(slug, title, body, publicationDate, authors, tags string) { - assertIsModerator() - - tagList := strings.Split(tags, ",") - authorList := strings.Split(authors, ",") - - err := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList) - checkErr(err) -} - -func isAdmin(addr std.Address) bool { - return addr == adminAddr -} - -func isModerator(addr std.Address) bool { - _, found := moderatorList.Get(addr.String()) - return found -} - -func assertIsAdmin() { - caller := std.GetOrigCaller() - if !isAdmin(caller) { - panic("access restricted.") - } -} - -func assertIsModerator() { - caller := std.GetOrigCaller() - if isAdmin(caller) || isModerator(caller) { - return - } - panic("access restricted") -} - -func assertNotInPause() { - if inPause { - panic("access restricted (pause)") - } -} - -func checkErr(err error) { - if err != nil { - panic(err) - } -} diff --git a/examples/gno.land/r/moul/present/present.gno b/examples/gno.land/r/moul/present/present.gno new file mode 100644 index 00000000000..a2101946c83 --- /dev/null +++ b/examples/gno.land/r/moul/present/present.gno @@ -0,0 +1,353 @@ +package present + +import ( + "net/url" + "std" + "strconv" + "strings" + "time" + + "gno.land/p/demo/avl/pager" + "gno.land/p/demo/ownable" + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/collection" + "gno.land/p/moul/md" + "gno.land/p/moul/mdtable" + "gno.land/p/moul/realmpath" + "gno.land/p/moul/txlink" +) + +type Presentation struct { + Slug string + Title string + Event string + Author string + Uploader std.Address + Date time.Time + Content string + EditDate time.Time + NumSlides int +} + +var ( + presentations *collection.Collection + Ownable *ownable.Ownable +) + +func init() { + presentations = collection.New() + // for /view and /slides + presentations.AddIndex("slug", func(v any) string { + return v.(*Presentation).Slug + }, collection.UniqueIndex) + + // for table sorting + presentations.AddIndex("date", func(v any) string { + return v.(*Presentation).Date.String() + }, collection.DefaultIndex) + presentations.AddIndex("author", func(v any) string { + return v.(*Presentation).Author + }, collection.DefaultIndex) + presentations.AddIndex("title", func(v any) string { + return v.(*Presentation).Title + }, collection.DefaultIndex) + + Ownable = ownable.New() +} + +// Render handles the realm's rendering logic +func Render(path string) string { + req := realmpath.Parse(path) + + // Get slug from path + slug := req.PathPart(0) + + // List view (home) + if slug == "" { + return renderList(req) + } + + // Slides view + if req.PathPart(1) == "slides" { + page := 1 + if pageStr := req.Query.Get("page"); pageStr != "" { + var err error + page, err = strconv.Atoi(pageStr) + if err != nil { + return "400: invalid page number" + } + } + return renderSlides(slug, page) + } + + // Regular view + return renderView(slug) +} + +// Set adds or updates a presentation +func Set(slug, title, event, author, date, content string) string { + Ownable.AssertCallerIsOwner() + + parsedDate, err := time.Parse("2006-01-02", date) + if err != nil { + return "400: invalid date format (expected: YYYY-MM-DD)" + } + + numSlides := 1 // Count intro slide + for _, line := range strings.Split(content, "\n") { + if strings.HasPrefix(line, "## ") { + numSlides++ + } + } + numSlides++ // Count thank you slide + + p := &Presentation{ + Slug: slug, + Title: title, + Event: event, + Author: author, + Uploader: std.PreviousRealm().Address(), + Date: parsedDate, + Content: content, + EditDate: time.Now(), + NumSlides: numSlides, + } + + presentations.Set(p) + return "presentation saved successfully" +} + +// Delete removes a presentation +func Delete(slug string) string { + Ownable.AssertCallerIsOwner() + + entry := presentations.GetFirst("slug", slug) + if entry == nil { + return "404: presentation not found" + } + + // XXX: consider this: + // if entry.Obj.(*Presentation).Uploader != std.PreviousRealm().Address() { + // return "401: unauthorized - only the uploader can delete their presentations" + // } + + // Convert the entry's ID from string to uint64 and delete + numericID, err := seqid.FromString(entry.ID) + if err != nil { + return "500: invalid entry ID format" + } + + presentations.Delete(uint64(numericID)) + return "presentation deleted successfully" +} + +func renderList(req *realmpath.Request) string { + var out strings.Builder + out.WriteString(md.H1("Presentations")) + + // Setup pager + index := presentations.GetIndex(getSortField(req)) + pgr := pager.NewPager(index, 10, isSortReversed(req)) + + // Get current page + page := pgr.MustGetPageByPath(req.String()) + + // Create table + dateColumn := renderSortLink(req, "date", "Date") + titleColumn := renderSortLink(req, "title", "Title") + authorColumn := renderSortLink(req, "author", "Author") + table := mdtable.Table{ + Headers: []string{dateColumn, titleColumn, "Event", authorColumn, "Slides"}, + } + + // Add rows from current page + for _, item := range page.Items { + // Get the actual presentation using the ID from the index + // XXX: improve p/moul/collection to make this more convenient. + // - no need to make per-id lookup. + // - transparently support multi-values. + // - integrate a sortable pager? + var ids []string + if ids_, ok := item.Value.([]string); ok { + ids = ids_ + } else if id, ok := item.Value.(string); ok { + ids = []string{id} + } + + for _, id := range ids { + entry := presentations.GetFirst(collection.IDIndex, id) + if entry == nil { + continue + } + p := entry.Obj.(*Presentation) + + table.Append([]string{ + p.Date.Format("2006-01-02"), + md.Link(p.Title, localPath(p.Slug, nil)), + p.Event, + p.Author, + ufmt.Sprintf("%d", p.NumSlides), + }) + } + } + + out.WriteString(table.String()) + out.WriteString(page.Picker()) // XXX: picker is not preserving the previous flags, should take "req" as argument. + return out.String() +} + +func (p *Presentation) FirstSlide() string { + var out strings.Builder + out.WriteString(md.H1(p.Title)) + out.WriteString(md.Paragraph(md.Bold(p.Event) + ", " + p.Date.Format("2 Jan 2006"))) + out.WriteString(md.Paragraph("by " + md.Bold(p.Author))) // XXX: link to u/? + return out.String() +} + +func (p *Presentation) LastSlide() string { + var out strings.Builder + out.WriteString(md.H1(p.Title)) + out.WriteString(md.H2("Thank You!")) + out.WriteString(md.Paragraph(p.Author)) + fullPath := "https://" + std.ChainDomain() + localPath(p.Slug, nil) + out.WriteString(md.Paragraph("🔗 " + md.Link(fullPath, fullPath))) + // XXX: QRCode + return out.String() +} + +func renderView(slug string) string { + if slug == "" { + return "400: missing presentation slug" + } + + entry := presentations.GetFirst("slug", slug) + if entry == nil { + return "404: presentation not found" + } + + p := entry.Obj.(*Presentation) + var out strings.Builder + + // Header using FirstSlide helper + out.WriteString(p.FirstSlide()) + + // Slide mode link + out.WriteString(md.Link("View as slides", localPath(p.Slug+"/slides", nil)) + "\n\n") + out.WriteString(md.HorizontalRule()) + out.WriteString(md.Paragraph(p.Content)) + + // Metadata footer + out.WriteString(md.HorizontalRule()) + out.WriteString(ufmt.Sprintf("Last edited: %s\n\n", p.EditDate.Format("2006-01-02 15:04:05"))) + out.WriteString(ufmt.Sprintf("Uploader: `%s`\n\n", p.Uploader)) + out.WriteString(ufmt.Sprintf("Number of slides: %d\n\n", p.NumSlides)) + + // Admin actions + // XXX: consider a dynamic toggle for admin actions + editLink := txlink.Call("Set", + "slug", p.Slug, + "title", p.Title, + "author", p.Author, + "event", p.Event, + "date", p.Date.Format("2006-01-02"), + ) + deleteLink := txlink.Call("Delete", "slug", p.Slug) + out.WriteString(md.Paragraph(md.Link("Edit", editLink) + " | " + md.Link("Delete", deleteLink))) + + return out.String() +} + +// renderSlidesNavigation returns the navigation bar for slides +func renderSlidesNavigation(slug string, currentPage, totalSlides int) string { + var out strings.Builder + if currentPage > 1 { + prevLink := localPath(slug+"/slides", url.Values{"page": {ufmt.Sprintf("%d", currentPage-1)}}) + out.WriteString(md.Link("← Prev", prevLink) + " ") + } + out.WriteString(ufmt.Sprintf("| %d/%d |", currentPage, totalSlides)) + if currentPage < totalSlides { + nextLink := localPath(slug+"/slides", url.Values{"page": {ufmt.Sprintf("%d", currentPage+1)}}) + out.WriteString(" " + md.Link("Next →", nextLink)) + } + return md.Paragraph(out.String()) +} + +func renderSlides(slug string, currentPage int) string { + if slug == "" { + return "400: missing presentation ID" + } + + entry := presentations.GetFirst("slug", slug) + if entry == nil { + return "404: presentation not found" + } + + p := entry.Obj.(*Presentation) + slides := strings.Split("\n"+p.Content, "\n## ") + if currentPage < 1 || currentPage > p.NumSlides { + return "404: invalid slide number" + } + + var out strings.Builder + + // Display current slide + if currentPage == 1 { + out.WriteString(p.FirstSlide()) + } else if currentPage == p.NumSlides { + out.WriteString(p.LastSlide()) + } else { + out.WriteString(md.H1(p.Title)) + out.WriteString("## " + slides[currentPage-1] + "\n\n") + } + + out.WriteString(renderSlidesNavigation(slug, currentPage, p.NumSlides)) + return out.String() +} + +// Helper functions for sorting and pagination +func getSortField(req *realmpath.Request) string { + field := req.Query.Get("sort") + switch field { + case "date", "slug", "author", "title": + return field + } + return "date" +} + +func isSortReversed(req *realmpath.Request) bool { + return req.Query.Get("order") != "asc" +} + +func renderSortLink(req *realmpath.Request, field, label string) string { + currentField := getSortField(req) + currentOrder := req.Query.Get("order") + + newOrder := "desc" + if field == currentField && currentOrder != "asc" { + newOrder = "asc" + } + + query := req.Query + query.Set("sort", field) + query.Set("order", newOrder) + + if field == currentField { + if newOrder == "asc" { + label += " ↑" + } else { + label += " ↓" + } + } + + return md.Link(label, "?"+query.Encode()) +} + +// helper to create local realm links +func localPath(path string, query url.Values) string { + req := &realmpath.Request{ + Path: path, + Query: query, + } + return req.String() +} diff --git a/examples/gno.land/r/moul/present/present_filetest.gno b/examples/gno.land/r/moul/present/present_filetest.gno new file mode 100644 index 00000000000..7e9385454b9 --- /dev/null +++ b/examples/gno.land/r/moul/present/present_filetest.gno @@ -0,0 +1,233 @@ +package main + +import ( + "gno.land/r/moul/present" +) + +func main() { + // Cleanup initial state + ret := present.Delete("demo") + if ret != "presentation deleted successfully" { + panic("internal error") + } + + // Create presentations with IDs from 10-20 + presentations := []struct { + id string + title string + event string + author string + date string + content string + }{ + {"s10", "title10", "event3", "author1", "2024-01-01", "## s10.0\n## s10.1"}, + {"s11", "title11", "event1", "author2", "2024-01-15", "## s11.0\n## s11.1"}, + {"s12", "title12", "event2", "author1", "2024-02-01", "## s12.0\n## s12.1"}, + {"s13", "title13", "event1", "author3", "2024-01-20", "## s13.0\n## s13.1"}, + {"s14", "title14", "event3", "author2", "2024-03-01", "## s14.0\n## s14.1"}, + {"s15", "title15", "event2", "author1", "2024-02-15", "## s15.0\n## s15.1\n## s15.2"}, + {"s16", "title16", "event1", "author4", "2024-03-15", "## s16.0\n## s16.1"}, + {"s17", "title17", "event3", "author2", "2024-01-10", "## s17.0\n## s17.1"}, + {"s18", "title18", "event2", "author3", "2024-02-20", "## s18.0\n## s18.1"}, + {"s19", "title19", "event1", "author1", "2024-03-10", "## s19.0\n## s19.1"}, + {"s20", "title20", "event3", "author4", "2024-01-05", "## s20.0\n## s20.1"}, + } + + for _, p := range presentations { + result := present.Set(p.id, p.title, p.event, p.author, p.date, p.content) + if result != "presentation saved successfully" { + panic("failed to add presentation: " + result) + } + } + + // Test different sorting scenarios + printRender("") // default + printRender("?order=asc&sort=date") // by date ascending + printRender("?order=asc&sort=title") // by title ascending + printRender("?order=asc&sort=author") // by author ascending (multiple entries per author) + + // Test pagination + printRender("?order=asc&sort=title&page=2") // second page + + // Test view + printRender("s15") // view by slug + + // Test slides + printRender("s15/slides") // slides by slug + printRender("s15/slides?page=2") // slides by slug, second page + printRender("s15/slides?page=3") // slides by slug, third page + printRender("s15/slides?page=4") // slides by slug, fourth page + printRender("s15/slides?page=5") // slides by slug, fifth page +} + +// Helper function to print path and render result +func printRender(path string) { + println("+-------------------------------") + println("| PATH:", path) + println("| RESULT:\n" + present.Render(path) + "\n") +} + +// Output: +// +------------------------------- +// | PATH: +// | RESULT: +// # Presentations +// | [Date ↑](?order=asc&sort=date) | [Title](?order=desc&sort=title) | Event | [Author](?order=desc&sort=author) | Slides | +// | --- | --- | --- | --- | --- | +// | 2024-03-15 | [title16](/r/moul/present:s16) | event1 | author4 | 4 | +// | 2024-03-10 | [title19](/r/moul/present:s19) | event1 | author1 | 4 | +// | 2024-03-01 | [title14](/r/moul/present:s14) | event3 | author2 | 4 | +// | 2024-02-20 | [title18](/r/moul/present:s18) | event2 | author3 | 4 | +// | 2024-02-15 | [title15](/r/moul/present:s15) | event2 | author1 | 5 | +// | 2024-02-01 | [title12](/r/moul/present:s12) | event2 | author1 | 4 | +// | 2024-01-20 | [title13](/r/moul/present:s13) | event1 | author3 | 4 | +// | 2024-01-15 | [title11](/r/moul/present:s11) | event1 | author2 | 4 | +// | 2024-01-10 | [title17](/r/moul/present:s17) | event3 | author2 | 4 | +// | 2024-01-05 | [title20](/r/moul/present:s20) | event3 | author4 | 4 | +// **1** | [2](?page=2) +// +// +------------------------------- +// | PATH: ?order=asc&sort=date +// | RESULT: +// # Presentations +// | [Date ↓](?order=desc&sort=date) | [Title](?order=desc&sort=title) | Event | [Author](?order=desc&sort=author) | Slides | +// | --- | --- | --- | --- | --- | +// | 2024-01-01 | [title10](/r/moul/present:s10) | event3 | author1 | 4 | +// | 2024-01-05 | [title20](/r/moul/present:s20) | event3 | author4 | 4 | +// | 2024-01-10 | [title17](/r/moul/present:s17) | event3 | author2 | 4 | +// | 2024-01-15 | [title11](/r/moul/present:s11) | event1 | author2 | 4 | +// | 2024-01-20 | [title13](/r/moul/present:s13) | event1 | author3 | 4 | +// | 2024-02-01 | [title12](/r/moul/present:s12) | event2 | author1 | 4 | +// | 2024-02-15 | [title15](/r/moul/present:s15) | event2 | author1 | 5 | +// | 2024-02-20 | [title18](/r/moul/present:s18) | event2 | author3 | 4 | +// | 2024-03-01 | [title14](/r/moul/present:s14) | event3 | author2 | 4 | +// | 2024-03-10 | [title19](/r/moul/present:s19) | event1 | author1 | 4 | +// **1** | [2](?page=2) +// +// +------------------------------- +// | PATH: ?order=asc&sort=title +// | RESULT: +// # Presentations +// | [Date](?order=desc&sort=date) | [Title](?order=desc&sort=title) | Event | [Author](?order=desc&sort=author) | Slides | +// | --- | --- | --- | --- | --- | +// | 2024-01-01 | [title10](/r/moul/present:s10) | event3 | author1 | 4 | +// | 2024-01-15 | [title11](/r/moul/present:s11) | event1 | author2 | 4 | +// | 2024-02-01 | [title12](/r/moul/present:s12) | event2 | author1 | 4 | +// | 2024-01-20 | [title13](/r/moul/present:s13) | event1 | author3 | 4 | +// | 2024-03-01 | [title14](/r/moul/present:s14) | event3 | author2 | 4 | +// | 2024-02-15 | [title15](/r/moul/present:s15) | event2 | author1 | 5 | +// | 2024-03-15 | [title16](/r/moul/present:s16) | event1 | author4 | 4 | +// | 2024-01-10 | [title17](/r/moul/present:s17) | event3 | author2 | 4 | +// | 2024-02-20 | [title18](/r/moul/present:s18) | event2 | author3 | 4 | +// | 2024-03-10 | [title19](/r/moul/present:s19) | event1 | author1 | 4 | +// **1** | [2](?page=2) +// +// +------------------------------- +// | PATH: ?order=asc&sort=author +// | RESULT: +// # Presentations +// | [Date](?order=desc&sort=date) | [Title](?order=desc&sort=title) | Event | [Author](?order=desc&sort=author) | Slides | +// | --- | --- | --- | --- | --- | +// | 2024-01-01 | [title10](/r/moul/present:s10) | event3 | author1 | 4 | +// | 2024-02-01 | [title12](/r/moul/present:s12) | event2 | author1 | 4 | +// | 2024-02-15 | [title15](/r/moul/present:s15) | event2 | author1 | 5 | +// | 2024-03-10 | [title19](/r/moul/present:s19) | event1 | author1 | 4 | +// | 2024-01-15 | [title11](/r/moul/present:s11) | event1 | author2 | 4 | +// | 2024-03-01 | [title14](/r/moul/present:s14) | event3 | author2 | 4 | +// | 2024-01-10 | [title17](/r/moul/present:s17) | event3 | author2 | 4 | +// | 2024-01-20 | [title13](/r/moul/present:s13) | event1 | author3 | 4 | +// | 2024-02-20 | [title18](/r/moul/present:s18) | event2 | author3 | 4 | +// | 2024-03-15 | [title16](/r/moul/present:s16) | event1 | author4 | 4 | +// | 2024-01-05 | [title20](/r/moul/present:s20) | event3 | author4 | 4 | +// +// +// +------------------------------- +// | PATH: ?order=asc&sort=title&page=2 +// | RESULT: +// # Presentations +// | [Date](?order=desc&page=2&sort=date) | [Title](?order=desc&page=2&sort=title) | Event | [Author](?order=desc&page=2&sort=author) | Slides | +// | --- | --- | --- | --- | --- | +// | 2024-01-05 | [title20](/r/moul/present:s20) | event3 | author4 | 4 | +// [1](?page=1) | **2** +// +// +------------------------------- +// | PATH: s15 +// | RESULT: +// # title15 +// **event2**, 15 Feb 2024 +// +// by **author1** +// +// [View as slides](/r/moul/present:s15/slides) +// +// --- +// ## s15.0 +// ## s15.1 +// ## s15.2 +// +// --- +// Last edited: 2009-02-13 23:31:30 +// +// Uploader: `g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm` +// +// Number of slides: 5 +// +// [Edit](/r/moul/present$help&func=Set&author=author1&date=2024-02-15&event=event2&slug=s15&title=title15) | [Delete](/r/moul/present$help&func=Delete&slug=s15) +// +// +// +// +------------------------------- +// | PATH: s15/slides +// | RESULT: +// # title15 +// **event2**, 15 Feb 2024 +// +// by **author1** +// +// | 1/5 | [Next →](/r/moul/present:s15/slides?page=2) +// +// +// +// +------------------------------- +// | PATH: s15/slides?page=2 +// | RESULT: +// # title15 +// ## s15.0 +// +// [← Prev](/r/moul/present:s15/slides?page=1) | 2/5 | [Next →](/r/moul/present:s15/slides?page=3) +// +// +// +// +------------------------------- +// | PATH: s15/slides?page=3 +// | RESULT: +// # title15 +// ## s15.1 +// +// [← Prev](/r/moul/present:s15/slides?page=2) | 3/5 | [Next →](/r/moul/present:s15/slides?page=4) +// +// +// +// +------------------------------- +// | PATH: s15/slides?page=4 +// | RESULT: +// # title15 +// ## s15.2 +// +// [← Prev](/r/moul/present:s15/slides?page=3) | 4/5 | [Next →](/r/moul/present:s15/slides?page=5) +// +// +// +// +------------------------------- +// | PATH: s15/slides?page=5 +// | RESULT: +// # title15 +// ## Thank You! +// author1 +// +// 🔗 [https://tests\.gno\.land/r/moul/present:s15](https://tests.gno.land/r/moul/present:s15) +// +// [← Prev](/r/moul/present:s15/slides?page=4) | 5/5 | +// +// +// diff --git a/examples/gno.land/r/moul/present/present_init.gno b/examples/gno.land/r/moul/present/present_init.gno new file mode 100644 index 00000000000..b103bdf8cd6 --- /dev/null +++ b/examples/gno.land/r/moul/present/present_init.gno @@ -0,0 +1,25 @@ +package present + +func init() { + _ = Set( + "demo", // id + "Demo Slides", // title + "Demo Event", // event + "@demo", // author + "2025-02-02", // date + `## Slide One +- Point A +- Point B +- Point C + +## Slide Two +- Feature 1 +- Feature 2 +- Feature 3 + +## Slide Three +- Next step +- Another step +- Final step`, + ) +} diff --git a/examples/gno.land/r/moul/present/present_miami23.gno b/examples/gno.land/r/moul/present/present_miami23.gno deleted file mode 100644 index ca2160de3a9..00000000000 --- a/examples/gno.land/r/moul/present/present_miami23.gno +++ /dev/null @@ -1,42 +0,0 @@ -package present - -func init() { - path := "miami23" - title := "Portal Loop Demo (Miami 2023)" - body := ` -Rendered by Gno. - -[Source (WIP)](https://github.com/gnolang/gno/pull/1176) - -## Portal Loop - -- DONE: Dynamic homepage, key pages, aliases, and redirects. -- TODO: Deploy with history, complete worxdao v0. -- Will replace the static gno.land site. -- Enhances local development. - -[GitHub Issue](https://github.com/gnolang/gno/issues/1108) - -## Roadmap - -- Crafting the roadmap this week, open to collaboration. -- Combining onchain (portal loop) and offchain (GitHub). -- Next week: Unveiling the official v0 roadmap. - -## Teams, DAOs, Projects - -- Developing worxDAO contracts for directories of projects and teams. -- GitHub teams and projects align with this structure. -- CODEOWNER file updates coming. -- Initial teams announced next week. - -## Tech Team Retreat Plan - -- Continue Portal Loop. -- Consider dApp development. -- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/). -- Engage in workshops. -- Connect and have fun with colleagues. -` - _ = b.NewPost(adminAddr, path, title, body, "2023-10-15T13:17:24Z", []string{"moul"}, []string{"demo", "portal-loop", "miami"}) -} diff --git a/examples/gno.land/r/moul/present/present_miami23_filetest.gno b/examples/gno.land/r/moul/present/present_miami23_filetest.gno deleted file mode 100644 index 09d332ec6e4..00000000000 --- a/examples/gno.land/r/moul/present/present_miami23_filetest.gno +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "gno.land/r/moul/present" -) - -func main() { - println(present.Render("")) - println("------------------------------------") - println(present.Render("p/miami23")) -} diff --git a/examples/gno.land/r/moul/present/presentations.gno b/examples/gno.land/r/moul/present/presentations.gno deleted file mode 100644 index c5529804751..00000000000 --- a/examples/gno.land/r/moul/present/presentations.gno +++ /dev/null @@ -1,17 +0,0 @@ -package present - -import ( - "gno.land/p/demo/blog" -) - -// TODO: switch from p/blog to p/present - -var b = &blog.Blog{ - Title: "Manfred's Presentations", - Prefix: "/r/moul/present:", - NoBreadcrumb: true, -} - -func Render(path string) string { - return b.Render(path) -} diff --git a/examples/gno.land/r/mouss/config/config.gno b/examples/gno.land/r/mouss/config/config.gno new file mode 100644 index 00000000000..d86b0606681 --- /dev/null +++ b/examples/gno.land/r/mouss/config/config.gno @@ -0,0 +1,37 @@ +package config + +import ( + "errors" + "std" + + "gno.land/p/demo/ownable" +) + +var ( + OwnableMain = ownable.NewWithAddress("g1wq2h93ppkf2gkgncz5unayrsmt7pl8npktnznd") + OwnableBackup = ownable.NewWithAddress("g1hrfvdh7jdvnlxpk2y20tp3scj9jqal3zzu7wjz") + + ErrUnauthorized = errors.New("config: unauthorized") +) + +func SetMainAddr(addr std.Address) error { + return OwnableMain.TransferOwnership(addr) +} + +func SetBackupAddr(addr std.Address) error { + return OwnableBackup.TransferOwnership(addr) +} + +func IsAuthorized(addr std.Address) bool { + return addr == OwnableMain.Owner() || addr == OwnableBackup.Owner() +} + +func Render(path string) string { + out := "# mouss configuration\n\n" + + out += "## Authorized Addresses\n\n" + out += "- main: " + OwnableMain.Owner().String() + "\n" + out += "- backup: " + OwnableBackup.Owner().String() + "\n\n" + + return out +} diff --git a/examples/gno.land/r/mouss/config/config_test.gno b/examples/gno.land/r/mouss/config/config_test.gno new file mode 100644 index 00000000000..0c070c0a7a9 --- /dev/null +++ b/examples/gno.land/r/mouss/config/config_test.gno @@ -0,0 +1,88 @@ +package config + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +var ( + mainAddr = std.Address("g1wq2h93ppkf2gkgncz5unayrsmt7pl8npktnznd") + backupAddr = std.Address("g1hrfvdh7jdvnlxpk2y20tp3scj9jqal3zzu7wjz") + + addr1 = testutils.TestAddress("addr1") + addr2 = testutils.TestAddress("addr2") + addr3 = testutils.TestAddress("addr3") +) + +func TestInitialOwnership(t *testing.T) { + uassert.Equal(t, OwnableMain.Owner(), mainAddr) + uassert.Equal(t, OwnableBackup.Owner(), backupAddr) +} + +func TestIsAuthorized(t *testing.T) { + tests := []struct { + name string + addr std.Address + want bool + }{ + {"main address is authorized", mainAddr, true}, + {"backup address is authorized", backupAddr, true}, + {"random address not authorized", addr3, false}, + {"empty address not authorized", "", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := IsAuthorized(tt.addr) + uassert.Equal(t, got, tt.want) + }) + } +} + +func TestSetMainAddr(t *testing.T) { + std.TestSetOriginCaller(mainAddr) + + // Test successful transfer + err := SetMainAddr(addr2) + urequire.NoError(t, err) + uassert.Equal(t, OwnableMain.Owner(), addr2) + + // Test unauthorized transfer + std.TestSetOriginCaller(addr3) + err = SetMainAddr(addr1) + uassert.ErrorContains(t, err, "ownable: caller is not owner") + + // Test invalid address + std.TestSetOriginCaller(addr2) + err = SetMainAddr("") + uassert.ErrorContains(t, err, "ownable: new owner address is invalid") + + // Reset state + std.TestSetOriginCaller(addr2) + err = SetMainAddr(mainAddr) + urequire.NoError(t, err) +} + +func TestSetBackupAddr(t *testing.T) { + std.TestSetOriginCaller(backupAddr) + + err := SetBackupAddr(addr2) + urequire.NoError(t, err) + uassert.Equal(t, OwnableBackup.Owner(), addr2) + + std.TestSetOriginCaller(addr3) + err = SetBackupAddr(addr1) + uassert.ErrorContains(t, err, "ownable: caller is not owner") + + std.TestSetOriginCaller(addr2) + err = SetBackupAddr("") + uassert.ErrorContains(t, err, "ownable: new owner address is invalid") + + std.TestSetOriginCaller(addr2) + err = SetBackupAddr(backupAddr) + urequire.NoError(t, err) +} diff --git a/examples/gno.land/r/mouss/config/gno.mod b/examples/gno.land/r/mouss/config/gno.mod new file mode 100644 index 00000000000..33b71de8f2c --- /dev/null +++ b/examples/gno.land/r/mouss/config/gno.mod @@ -0,0 +1 @@ +module gno.land/r/mouss/config diff --git a/examples/gno.land/r/mouss/home/gno.mod b/examples/gno.land/r/mouss/home/gno.mod new file mode 100644 index 00000000000..a4ebfa34d16 --- /dev/null +++ b/examples/gno.land/r/mouss/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/mouss/home diff --git a/examples/gno.land/r/mouss/home/home.gno b/examples/gno.land/r/mouss/home/home.gno new file mode 100644 index 00000000000..4e7c5ee0dce --- /dev/null +++ b/examples/gno.land/r/mouss/home/home.gno @@ -0,0 +1,251 @@ +package home + +import ( + "std" + "strconv" + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/demo/mux" + "gno.land/p/demo/ufmt" + "gno.land/p/moul/addrset" + "gno.land/p/moul/md" + "gno.land/r/leon/hof" + "gno.land/r/mouss/config" +) + +// Profile represents my personal profile information. +type Profile struct { + AboutMe string + Avatar string + Email string + Github string + LinkedIn string + Followers *addrset.Set // Set of followers addresses. +} + +// Recipe represents a cooking recipe with its details. +type Recipe struct { + Name string + Origin string + Author std.Address + Ingredients string + Instructions string + Tips string +} + +const ( + realmURL = "/r/mouss/home" + rec = realmURL + ":recipe/" + gnoArt = ` + -==++. + *@@@@= @- -@ + #@@@@@: -==-.-- :-::===: .-++-. @- .===:.- .-.-==- .===:=@ + #@@@@@@@: -@@%**%@@ #@@#*#@@- *@@**@@* @- +%=::-*@ +@=-:-@* +%=::-*@ + +@%#**#%@@ %@+ :@@ *@+ #@=+@% %@+ @= :@: -@ +% +%.@: -@ + -: - *@%:..+@@ *@+ #@=-@@: :@@= @- .@= =@ +@ *%.@= =@ + --:==+=-:=. =%@%#*@@ *@+ #@+ =%@%%@%= #* %#=.:%*===*@ +% +% -%*===*@ + -++++=++++. =-:::*@# . . .::. .. :: .:: . . .:: . + .-=+++=: .*###%#= + :: +` +) + +var ( + router = mux.NewRouter() + profile Profile + recipes = avl.NewTree() + margheritaPizza *Recipe +) + +// init initializes the router with the home page and recipe routes +// sets up my profile information, and my recipe +// and registers the home page in the hall of fame. +func init() { + router.HandleFunc("", renderHomepage) + router.HandleFunc("recipe/", renderRecipes) + router.HandleFunc("recipe/{name}", renderRecipe) + profile = Profile{ + AboutMe: "👋 I'm Mustapha, a contributor to gno.land project from France. I'm passionate about coding, exploring new technologies, and contributing to open-source projects. Besides my tech journey, I'm also a pizzaiolo 🍕 who loves cooking and savoring good food.", + Avatar: "https://github.com/mous1985/assets/blob/master/avatar.png?raw=true", + Email: "mustapha.benazzouz@outlook.fr", + Github: "https://github.com/mous1985", + LinkedIn: "https://www.linkedin.com/in/mustapha-benazzouz-88646887/", + Followers: &addrset.Set{}, + } + margheritaPizza = &Recipe{ + Name: "Authentic Margherita Pizza 🤌", + Origin: "Naples, 🇮🇹", + Author: config.OwnableMain.Owner(), + Ingredients: " 1kg 00 flour\n 500ml water\n 3g fresh yeast\n 20g sea salt\n San Marzano tomatoes\n Fresh buffalo mozzarella\n Fresh basil\n Extra virgin olive oil", + Instructions: " Mix flour and water until incorporated\n Add yeast and salt, knead for 20 minutes\n Let rise for 2 hours at room temperature\n Divide into 250g balls\n Cold ferment for 24-48 hours\n Shape by hand, being gentle with the dough\n Top with crushed tomatoes, torn mozzarella, and basil\n Cook at 450°C for 60-90 seconds", + Tips: "Use a pizza steel or stone preheated for at least 1 hour. The dough should be soft and extensible. For best results, cook in a wood-fired oven.", + } + hof.Register() +} + +// AddRecipe adds a new recipe in recipe page by users +func AddRecipe(name, origin, ingredients, instructions, tips string) string { + if err := validateRecipe(name, ingredients, instructions); err != nil { + panic(err) + } + recipe := &Recipe{ + Name: name, + Origin: origin, + Author: std.PreviousRealm().Address(), + Ingredients: ingredients, + Instructions: instructions, + Tips: tips, + } + recipes.Set(name, recipe) + return "Recipe added successfully" +} + +func UpdateAboutMe(about string) error { + if !config.IsAuthorized(std.PreviousRealm().Address()) { + panic(config.ErrUnauthorized) + } + profile.AboutMe = about + return nil +} + +func UpdateAvatar(avatar string) error { + if !config.IsAuthorized(std.PreviousRealm().Address()) { + panic(config.ErrUnauthorized) + } + profile.Avatar = avatar + return nil +} + +// validateRecipe checks if the provided recipe details are valid. +func validateRecipe(name, ingredients, instructions string) error { + if name == "" { + return ufmt.Errorf("recipe name cannot be empty") + } + if len(ingredients) == 0 { + return ufmt.Errorf("ingredients cannot be empty") + } + if len(instructions) == 0 { + return ufmt.Errorf("instructions cannot be empty") + } + return nil +} + +// Follow allows a users to follow my home page. +// If the caller is admin it returns error. +func Follow() error { + caller := std.PreviousRealm().Address() + + if caller == config.OwnableMain.Owner() { + return ufmt.Errorf("you cannot follow yourself") + } + if profile.Followers.Add(caller) { + return nil + } + return ufmt.Errorf("you are already following") + +} + +// Unfollow allows a user to unfollow my home page. +func Unfollow() error { + caller := std.PreviousRealm().Address() + + if profile.Followers.Remove(caller) { + return nil + } + return ufmt.Errorf("you are not following") +} + +// renderRecipes renders the list of recipes. +func renderRecipes(res *mux.ResponseWriter, req *mux.Request) { + var out string + out += Header() + out += "## World Kitchen\n\n------\n\n" + + // Link to margarita pizza recipe + out += "### Available Recipes:\n\n" + out += "* " + md.Link(margheritaPizza.Name, rec+"margheritaPizza") + "By : " + string(margheritaPizza.Author) + "\n" + + // The list of all other recipes with clickable links + if recipes.Size() > 0 { + recipes.Iterate("", "", func(key string, value interface{}) bool { + recipe := value.(*Recipe) + out += "* " + md.Link(recipe.Name, rec+recipe.Name) + " By : " + recipe.Author.String() + "\n" + return false // continue iterating + }) + out += "\n------\n\n" + } else { + out += "\nNo additional recipes yet. Be the first to add one!\n" + } + res.Write(out) +} + +// renderRecipe renders the recipe details. +func renderRecipe(res *mux.ResponseWriter, req *mux.Request) { + name := req.GetVar("name") + if name == "margheritaPizza" { + res.Write(margheritaPizza.Render()) + return + } + value, exists := recipes.Get(name) + if !exists { + res.Write("Recipe not found") + return + } + recipe := value.(*Recipe) + res.Write(recipe.Render()) +} + +func (r Recipe) Render() string { + var out string + out += Header() + out += md.H2(r.Name) + out += md.Bold("Author:") + "\n" + r.Author.String() + "\n\n" + out += md.Bold("Origin:") + "\n" + r.Origin + "\n\n" + out += md.Bold("Ingredients:") + "\n" + md.BulletList(strings.Split(r.Ingredients, "\n")) + "\n\n" + out += md.Bold("Instructions:") + "\n" + md.OrderedList(strings.Split(r.Instructions, "\n")) + "\n\n" + if r.Tips != "" { + out += md.Italic("💡 Tips:"+"\n"+r.Tips) + "\n\n" + } + out += md.HorizontalRule() + "\n" + return out +} + +func renderHomepage(res *mux.ResponseWriter, req *mux.Request) { + var out string + out += Header() + out += profile.Render() + res.Write(out) +} + +func (p Profile) Render() string { + var out string + out += md.H1("Welcome to my Homepage") + "\n\n" + md.HorizontalRule() + "\n\n" + out += "```\n" + out += gnoArt + out += "```\n------" + out += md.HorizontalRule() + "\n\n" + md.H2("About Me") + "\n\n" + out += md.Image("avatar", p.Avatar) + "\n\n" + out += p.AboutMe + "\n\n" + md.HorizontalRule() + "\n\n" + out += md.H3("Contact") + "\n\n" + out += md.BulletList([]string{ + "Email: " + p.Email, + "GitHub: " + md.Link("@mous1985", p.Github), + "LinkedIn: " + md.Link("Mustapha", p.LinkedIn), + }) + out += "\n\n" + md.Bold("👤 Followers: ") + strconv.Itoa(p.Followers.Size()) + return out +} + +func Header() string { + navItems := []string{ + md.Link("Home", realmURL), + md.Link("World Kitchen", rec), + md.Link("Hackerspace", "https://github.com/gnolang/hackerspace/issues/86#issuecomment-2535795751"), + } + return strings.Join(navItems, " | ") + "\n\n" + md.HorizontalRule() + "\n\n" +} + +func Render(path string) string { + return router.Render(path) +} diff --git a/examples/gno.land/r/mouss/home/home_test.gno b/examples/gno.land/r/mouss/home/home_test.gno new file mode 100644 index 00000000000..6e69dc6563c --- /dev/null +++ b/examples/gno.land/r/mouss/home/home_test.gno @@ -0,0 +1,99 @@ +package home + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/r/mouss/config" +) + +var ( + user1 = testutils.TestAddress("user1") + user2 = testutils.TestAddress("user2") + mainAddr = config.OwnableMain.Owner() +) + +func TestProfile(t *testing.T) { + uassert.NotEmpty(t, profile.AboutMe, "AboutMe should not be empty") + uassert.NotEmpty(t, profile.Avatar, "Avatar should not be empty") + uassert.NotEmpty(t, profile.Email, "Email should not be empty") + uassert.NotEmpty(t, profile.Github, "Github should not be empty") + uassert.NotEmpty(t, profile.LinkedIn, "LinkedIn should not be empty") +} + +func TestAddRecipe(t *testing.T) { + std.TestSetOriginCaller(user1) + name := "Test Recipe" + origin := "Test Origin" + ingredients := "Ingredient 1\nIngredient 2" + instructions := "Step 1\nStep 2" + tips := "Test Tips" + + result := AddRecipe(name, origin, ingredients, instructions, tips) + uassert.Equal(t, "Recipe added successfully", result) + uassert.Equal(t, 1, recipes.Size()) + value, exist := recipes.Get(name) + uassert.True(t, exist) + recipe := value.(*Recipe) + uassert.Equal(t, name, recipe.Name) + uassert.Equal(t, origin, recipe.Origin) + uassert.Equal(t, ingredients, recipe.Ingredients) + uassert.Equal(t, instructions, recipe.Instructions) + uassert.Equal(t, tips, recipe.Tips) + uassert.Equal(t, user1, recipe.Author) + + // Verify recipe is correctly stored in AVL tree with matching fields + var found bool + recipes.Iterate("", "", func(key string, value interface{}) bool { + if key == name { + found = true + foundRecipe := value.(*Recipe) + uassert.Equal(t, recipe.Name, foundRecipe.Name) + uassert.Equal(t, recipe.Origin, foundRecipe.Origin) + uassert.Equal(t, recipe.Ingredients, foundRecipe.Ingredients) + uassert.Equal(t, recipe.Instructions, foundRecipe.Instructions) + uassert.Equal(t, recipe.Tips, foundRecipe.Tips) + uassert.Equal(t, recipe.Author, foundRecipe.Author) + return true + } + return false + }) + uassert.Equal(t, true, found) +} + +func TestFollow(t *testing.T) { + // Test user following admin's profile + std.TestSetOriginCaller(user1) + err := Follow() + uassert.NoError(t, err, "user should be able to follow admin's profile") + + // Test admin trying to follow themselves + std.TestSetOriginCaller(mainAddr) + err = Follow() + uassert.Error(t, err, "you cannot follow yourself") + + // Test following same address twice + std.TestSetOriginCaller(user1) + err = Follow() + uassert.Error(t, err, "should not be able to follow same address twice") + + // Test multiple users following admin + std.TestSetOriginCaller(user2) + err = Follow() + uassert.NoError(t, err, "another user should be able to follow admin's profile") +} + +func TestUnfollow(t *testing.T) { + // Test successful unfollow + std.TestSetOriginCaller(user1) + err := Unfollow() + uassert.NoError(t, err) + uassert.False(t, profile.Followers.Has(user1)) + + // Test unfollowing when not following + err = Unfollow() + uassert.Error(t, err) + +} diff --git a/examples/gno.land/r/n2p5/haystack/haystack_test.gno b/examples/gno.land/r/n2p5/haystack/haystack_test.gno index 52dadf8bf9e..2a25649ff5a 100644 --- a/examples/gno.land/r/n2p5/haystack/haystack_test.gno +++ b/examples/gno.land/r/n2p5/haystack/haystack_test.gno @@ -31,14 +31,14 @@ func TestHaystack(t *testing.T) { n2, _ := genNeedleHex(2) n3, _ := genNeedleHex(3) - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) urequire.NotPanics(t, func() { Add(n1) }) urequire.PanicsWithMessage(t, haystack.ErrorDuplicateNeedle.Error(), func() { Add(n1) }) - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) urequire.NotPanics(t, func() { Add(n2) }) urequire.NotPanics(t, func() { Add(n3) }) }) @@ -49,14 +49,14 @@ func TestHaystack(t *testing.T) { n1, h1 := genNeedleHex(4) _, h2 := genNeedleHex(5) - std.TestSetOrigCaller(u1) + std.TestSetOriginCaller(u1) urequire.NotPanics(t, func() { Add(n1) }) urequire.NotPanics(t, func() { result := Get(h1) urequire.Equal(t, n1, result) }) - std.TestSetOrigCaller(u2) + std.TestSetOriginCaller(u2) urequire.NotPanics(t, func() { result := Get(h1) urequire.Equal(t, n1, result) diff --git a/examples/gno.land/r/n2p5/home/home.gno b/examples/gno.land/r/n2p5/home/home.gno index 69b82e86d68..d99ec4d3b7d 100644 --- a/examples/gno.land/r/n2p5/home/home.gno +++ b/examples/gno.land/r/n2p5/home/home.gno @@ -52,7 +52,7 @@ func Render(path string) string { // assertAdmin panics if the caller is not an admin as defined in the config realm. func assertAdmin() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() if !config.IsAdmin(caller) { panic("forbidden: must be admin") } diff --git a/examples/gno.land/r/n2p5/loci/loci.gno b/examples/gno.land/r/n2p5/loci/loci.gno index 36f282e729f..232de1e6459 100644 --- a/examples/gno.land/r/n2p5/loci/loci.gno +++ b/examples/gno.land/r/n2p5/loci/loci.gno @@ -23,7 +23,7 @@ func Set(value string) { panic(err) } store.Set(b) - std.Emit("SetValue", "ForAddr", string(std.PrevRealm().Addr())) + std.Emit("SetValue", "ForAddr", string(std.PreviousRealm().Address())) } // Get retrieves the value stored at the provided address and diff --git a/examples/gno.land/r/nemanya/config/config.gno b/examples/gno.land/r/nemanya/config/config.gno index 795e48c94c1..78fe329d5fe 100644 --- a/examples/gno.land/r/nemanya/config/config.gno +++ b/examples/gno.land/r/nemanya/config/config.gno @@ -52,7 +52,7 @@ func SetBackup(a std.Address) error { } func checkAuthorized() error { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() isAuthorized := caller == main || caller == backup if !isAuthorized { diff --git a/examples/gno.land/r/nemanya/home/home.gno b/examples/gno.land/r/nemanya/home/home.gno index 08e24baecfd..34545773f4d 100644 --- a/examples/gno.land/r/nemanya/home/home.gno +++ b/examples/gno.land/r/nemanya/home/home.gno @@ -137,7 +137,7 @@ func renderProjects(projectsMap map[string]Project, title string) string { } func UpdateLink(name, newURL string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -152,7 +152,7 @@ func UpdateLink(name, newURL string) { } func UpdateAboutMe(text string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -160,7 +160,7 @@ func UpdateAboutMe(text string) { } func AddGnoProject(name, description, url, imageURL string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } project := Project{ @@ -174,7 +174,7 @@ func AddGnoProject(name, description, url, imageURL string) { } func DeleteGnoProject(projectName string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -186,7 +186,7 @@ func DeleteGnoProject(projectName string) { } func AddOtherProject(name, description, url, imageURL string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } project := Project{ @@ -200,7 +200,7 @@ func AddOtherProject(name, description, url, imageURL string) { } func RemoveOtherProject(projectName string) { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } @@ -216,8 +216,8 @@ func isAuthorized(addr std.Address) bool { } func SponsorGnoProject(projectName string) { - address := std.GetOrigCaller() - amount := std.GetOrigSend() + address := std.OriginCaller() + amount := std.OriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") @@ -239,8 +239,8 @@ func SponsorGnoProject(projectName string) { } func SponsorOtherProject(projectName string) { - address := std.GetOrigCaller() - amount := std.GetOrigSend() + address := std.OriginCaller() + amount := std.OriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") @@ -262,12 +262,12 @@ func SponsorOtherProject(projectName string) { } func Withdraw() string { - if !isAuthorized(std.PrevRealm().Addr()) { + if !isAuthorized(std.PreviousRealm().Address()) { panic(config.ErrUnauthorized) } - banker := std.GetBanker(std.BankerTypeRealmSend) - realmAddress := std.GetOrigPkgAddr() + banker := std.NewBanker(std.BankerTypeRealmSend) + realmAddress := std.OriginPkgAddress() coins := banker.GetCoins(realmAddress) if len(coins) == 0 { diff --git a/examples/gno.land/r/stefann/fomo3d/errors.gno b/examples/gno.land/r/stefann/fomo3d/errors.gno new file mode 100644 index 00000000000..df70ab08c55 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/errors.gno @@ -0,0 +1,30 @@ +package fomo3d + +import "errors" + +var ( + // Game state errors + ErrGameInProgress = errors.New("fomo3d: game already in progress") + ErrGameNotInProgress = errors.New("fomo3d: game not in progress") + ErrGameEnded = errors.New("fomo3d: game has ended") + ErrGameTimeExpired = errors.New("fomo3d: game time expired") + ErrNoKeysPurchased = errors.New("fomo3d: no keys purchased") + ErrPlayerNotInGame = errors.New("fomo3d: player is not in the game") + + // Payment errors + ErrInvalidPayment = errors.New("fomo3d: must send ugnot only") + ErrInsufficientPayment = errors.New("fomo3d: insufficient payment for key") + + // Dividend errors + ErrNoDividendsToClaim = errors.New("fomo3d: no dividends to claim") + + // Fee errors + ErrNoFeesToClaim = errors.New("fomo3d: no owner fees to claim") + + // Resolution errors + ErrInvalidAddressOrName = errors.New("fomo3d: invalid address or unregistered username") + + // NFT errors + ErrUnauthorizedMint = errors.New("fomo3d: only the Fomo3D game realm can mint winner NFTs") + ErrZeroAddress = errors.New("fomo3d: zero address") +) diff --git a/examples/gno.land/r/stefann/fomo3d/events.gno b/examples/gno.land/r/stefann/fomo3d/events.gno new file mode 100644 index 00000000000..ea404466955 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/events.gno @@ -0,0 +1,94 @@ +package fomo3d + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +// Event names +const ( + // Game events + GameStartedEvent = "GameStarted" + GameEndedEvent = "GameEnded" + KeysPurchasedEvent = "KeysPurchased" + + // Player events + DividendsClaimedEvent = "DividendsClaimed" + + // Admin events + OwnerFeeClaimedEvent = "OwnerFeeClaimed" +) + +// Event keys +const ( + // Common keys + EventRoundKey = "round" + EventAmountKey = "amount" + + // Game keys + EventStartBlockKey = "startBlock" + EventEndBlockKey = "endBlock" + EventStartingPotKey = "startingPot" + EventWinnerKey = "winner" + EventJackpotKey = "jackpot" + + // Player keys + EventBuyerKey = "buyer" + EventNumKeysKey = "numKeys" + EventPriceKey = "price" + EventJackpotShareKey = "jackpotShare" + EventDividendShareKey = "dividendShare" + EventClaimerKey = "claimer" + + // Admin keys + EventOwnerKey = "owner" + EventPreviousOwnerKey = "previousOwner" + EventNewOwnerKey = "newOwner" +) + +func emitGameStarted(round, startBlock, endBlock, startingPot int64) { + std.Emit( + GameStartedEvent, + EventRoundKey, ufmt.Sprintf("%d", round), + EventStartBlockKey, ufmt.Sprintf("%d", startBlock), + EventEndBlockKey, ufmt.Sprintf("%d", endBlock), + EventStartingPotKey, ufmt.Sprintf("%d", startingPot), + ) +} + +func emitGameEnded(round int64, winner std.Address, jackpot int64) { + std.Emit( + GameEndedEvent, + EventRoundKey, ufmt.Sprintf("%d", round), + EventWinnerKey, winner.String(), + EventJackpotKey, ufmt.Sprintf("%d", jackpot), + ) +} + +func emitKeysPurchased(buyer std.Address, numKeys, price, jackpotShare, dividendShare int64) { + std.Emit( + KeysPurchasedEvent, + EventBuyerKey, buyer.String(), + EventNumKeysKey, ufmt.Sprintf("%d", numKeys), + EventPriceKey, ufmt.Sprintf("%d", price), + EventJackpotShareKey, ufmt.Sprintf("%d", jackpotShare), + EventDividendShareKey, ufmt.Sprintf("%d", dividendShare), + ) +} + +func emitDividendsClaimed(claimer std.Address, amount int64) { + std.Emit( + DividendsClaimedEvent, + EventClaimerKey, claimer.String(), + EventAmountKey, ufmt.Sprintf("%d", amount), + ) +} + +func emitOwnerFeeClaimed(owner std.Address, amount int64) { + std.Emit( + OwnerFeeClaimedEvent, + EventOwnerKey, owner.String(), + EventAmountKey, ufmt.Sprintf("%d", amount), + ) +} diff --git a/examples/gno.land/r/stefann/fomo3d/fomo3d.gno b/examples/gno.land/r/stefann/fomo3d/fomo3d.gno new file mode 100644 index 00000000000..dbb89f39af7 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/fomo3d.gno @@ -0,0 +1,358 @@ +package fomo3d + +import ( + "std" + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + + "gno.land/r/leon/hof" + "gno.land/r/sys/users" +) + +// FOMO3D (Fear Of Missing Out 3D) is a blockchain-based game that combines elements +// of a lottery and investment mechanics. Players purchase keys using GNOT tokens, +// where each key purchase: +// - Extends the game timer +// - Increases the key price by 1% +// - Makes the buyer the potential winner of the jackpot +// - Distributes dividends to all key holders +// +// Game Mechanics: +// - The last person to buy a key before the timer expires wins the jackpot (47% of all purchases) +// - Key holders earn dividends from each purchase (28% of all purchases) +// - 20% of purchases go to the next round's starting pot +// - 5% goes to development fee +// - Game ends when the timer expires +// +// Inspired by the original Ethereum FOMO3D game but implemented in Gno. + +const ( + MIN_KEY_PRICE int64 = 100000 // minimum key price in ugnot + TIME_EXTENSION int64 = 86400 // time extension in blocks when new key is bought (~24 hours @ 1s blocks) + + // Distribution percentages (total 100%) + JACKPOT_PERCENT int64 = 47 // 47% goes to jackpot + DIVIDENDS_PERCENT int64 = 28 // 28% distributed to key holders + NEXT_ROUND_POT int64 = 20 // 20% goes to next round's starting pot + OWNER_FEE_PERCENT int64 = 5 // 5% goes to contract owner +) + +type PlayerInfo struct { + Keys int64 // number of keys owned + Dividends int64 // unclaimed dividends in ugnot +} + +// GameState represents the current state of the FOMO3D game +type GameState struct { // TODO: Separate GameState and RoundState and save round history tree in GameState + StartBlock int64 // Block when the game started + EndBlock int64 // Block when the game will end + LastKeyBlock int64 // Block of last key purchase + LastBuyer std.Address // Address of last key buyer + Jackpot int64 // Current jackpot in ugnot + KeyPrice int64 // Current price of keys in ugnot + TotalKeys int64 // Total number of keys in circulation + Ended bool // Whether the game has ended + CurrentRound int64 // Current round number + NextPot int64 // Next round's starting pot + OwnerFee int64 // Accumulated owner fees + BuyKeysLink string // Link to BuyKeys function + ClaimDividendsLink string // Link to ClaimDividends function + StartGameLink string // Link to StartGame function +} + +var ( + gameState GameState + players *avl.Tree // maps address -> PlayerInfo + Ownable *ownable.Ownable +) + +func init() { + Ownable = ownable.New() + players = avl.NewTree() + gameState.Ended = true + hof.Register() +} + +// StartGame starts a new game round +func StartGame() { + if !gameState.Ended && gameState.StartBlock != 0 { + panic(ErrGameInProgress.Error()) + } + + gameState.CurrentRound++ + gameState.StartBlock = std.ChainHeight() + gameState.EndBlock = gameState.StartBlock + TIME_EXTENSION // Initial 24h window + gameState.LastKeyBlock = gameState.StartBlock + gameState.Jackpot = gameState.NextPot + gameState.NextPot = 0 + gameState.Ended = false + gameState.KeyPrice = MIN_KEY_PRICE + gameState.TotalKeys = 0 + + // Clear previous round's player data + players = avl.NewTree() + + emitGameStarted( + gameState.CurrentRound, + gameState.StartBlock, + gameState.EndBlock, + gameState.Jackpot, + ) +} + +// BuyKeys allows players to purchase keys +func BuyKeys() { + if gameState.Ended { + panic(ErrGameEnded.Error()) + } + + currentBlock := std.ChainHeight() + if currentBlock > gameState.EndBlock { + panic(ErrGameTimeExpired.Error()) + } + + // Get sent coins + sent := std.OriginSend() + if len(sent) != 1 || sent[0].Denom != "ugnot" { + panic(ErrInvalidPayment.Error()) + } + + payment := sent.AmountOf("ugnot") + if payment < gameState.KeyPrice { + panic(ErrInsufficientPayment.Error()) + } + + // Calculate number of keys that can be bought and actual cost + numKeys := payment / gameState.KeyPrice + actualCost := numKeys * gameState.KeyPrice + excess := payment - actualCost + + // Update buyer's info + buyer := std.PreviousRealm().Address() + var buyerInfo PlayerInfo + if info, exists := players.Get(buyer.String()); exists { + buyerInfo = info.(PlayerInfo) + } + + buyerInfo.Keys += numKeys + gameState.TotalKeys += numKeys + + // Distribute actual cost + jackpotShare := actualCost * JACKPOT_PERCENT / 100 + dividendShare := actualCost * DIVIDENDS_PERCENT / 100 + nextPotShare := actualCost * NEXT_ROUND_POT / 100 + ownerShare := actualCost * OWNER_FEE_PERCENT / 100 + + // Update pools + gameState.Jackpot += jackpotShare + gameState.NextPot += nextPotShare + gameState.OwnerFee += ownerShare + + // Return excess payment to buyer if any + if excess > 0 { + banker := std.NewBanker(std.BankerTypeOriginSend) + banker.SendCoins( + std.CurrentRealm().Address(), + buyer, + std.NewCoins(std.NewCoin("ugnot", excess)), + ) + } + + // Distribute dividends to all key holders + if players.Size() > 0 && gameState.TotalKeys > 0 { + dividendPerKey := dividendShare / gameState.TotalKeys + players.Iterate("", "", func(key string, value any) bool { + playerInfo := value.(PlayerInfo) + playerInfo.Dividends += playerInfo.Keys * dividendPerKey + players.Set(key, playerInfo) + return false + }) + } + + // Update game state + gameState.LastBuyer = buyer + gameState.LastKeyBlock = currentBlock + gameState.EndBlock = currentBlock + TIME_EXTENSION // Always extend 24h from current block + gameState.KeyPrice += (gameState.KeyPrice * numKeys) / 100 + + // Save buyer's updated info + players.Set(buyer.String(), buyerInfo) + + emitKeysPurchased( + buyer, + numKeys, + gameState.KeyPrice, + jackpotShare, + dividendShare, + ) +} + +// ClaimDividends allows players to withdraw their earned dividends +func ClaimDividends() { + caller := std.PreviousRealm().Address() + + info, exists := players.Get(caller.String()) + if !exists { + panic(ErrNoDividendsToClaim.Error()) + } + + playerInfo := info.(PlayerInfo) + if playerInfo.Dividends == 0 { + panic(ErrNoDividendsToClaim.Error()) + } + + // Reset dividends and send coins + amount := playerInfo.Dividends + playerInfo.Dividends = 0 + players.Set(caller.String(), playerInfo) + + banker := std.NewBanker(std.BankerTypeRealmSend) + banker.SendCoins( + std.CurrentRealm().Address(), + caller, + std.NewCoins(std.NewCoin("ugnot", amount)), + ) + + emitDividendsClaimed(caller, amount) +} + +// ClaimOwnerFee allows the owner to withdraw accumulated fees +func ClaimOwnerFee() { + Ownable.AssertCallerIsOwner() + + if gameState.OwnerFee == 0 { + panic(ErrNoFeesToClaim.Error()) + } + + amount := gameState.OwnerFee + gameState.OwnerFee = 0 + + banker := std.NewBanker(std.BankerTypeRealmSend) + banker.SendCoins( + std.CurrentRealm().Address(), + Ownable.Owner(), + std.NewCoins(std.NewCoin("ugnot", amount)), + ) + + emitOwnerFeeClaimed(Ownable.Owner(), amount) +} + +// EndGame ends the current round and distributes the jackpot +func EndGame() { + if gameState.Ended { + panic(ErrGameEnded.Error()) + } + + currentBlock := std.ChainHeight() + if currentBlock <= gameState.EndBlock { + panic(ErrGameNotInProgress.Error()) + } + + if gameState.LastBuyer == "" { + panic(ErrNoKeysPurchased.Error()) + } + + gameState.Ended = true + + // Send jackpot to winner + banker := std.NewBanker(std.BankerTypeRealmSend) + banker.SendCoins( + std.CurrentRealm().Address(), + gameState.LastBuyer, + std.NewCoins(std.NewCoin("ugnot", gameState.Jackpot)), + ) + + emitGameEnded( + gameState.CurrentRound, + gameState.LastBuyer, + gameState.Jackpot, + ) + + // Mint NFT for the winner + if err := mintRoundWinnerNFT(gameState.LastBuyer, gameState.CurrentRound); err != nil { + panic(err.Error()) + } +} + +// GetGameState returns current game state +func GetGameState() (int64, int64, int64, std.Address, int64, int64, int64, bool, int64, int64) { + return gameState.StartBlock, + gameState.EndBlock, + gameState.LastKeyBlock, + gameState.LastBuyer, + gameState.Jackpot, + gameState.KeyPrice, + gameState.TotalKeys, + gameState.Ended, + gameState.NextPot, + gameState.CurrentRound +} + +// GetOwnerInfo returns the owner address and unclaimed fees +func GetOwnerInfo() (std.Address, int64) { + return Ownable.Owner(), gameState.OwnerFee +} + +// Helper to convert string (address or username) to address +func stringToAddress(input string) std.Address { + // Check if input is valid address + addr := std.Address(input) + if addr.IsValid() { + return addr + } + + // Not an address, try to find namespace + if user, _ := users.ResolveName(input); user != nil { + return user.Addr() + } + + return "" +} + +func isPlayerInGame(addr std.Address) bool { + _, exists := players.Get(addr.String()) + return exists +} + +// GetPlayerInfo returns a player's keys and dividends +func GetPlayerInfo(addrOrName string) (int64, int64) { + addr := stringToAddress(addrOrName) + + if addr == "" { + panic(ErrInvalidAddressOrName.Error()) + } + + if !isPlayerInGame(addr) { + panic(ErrPlayerNotInGame.Error()) + } + + info, _ := players.Get(addr.String()) + playerInfo := info.(PlayerInfo) + return playerInfo.Keys, playerInfo.Dividends +} + +// Render handles the rendering of game state +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return RenderHome() + case c == 2 && parts[0] == "player": + if gameState.Ended { + return ufmt.Sprintf("🔴 Game has not started yet.\n\n Call [`StartGame()`](%s) to start a new round.\n\n", gameState.StartGameLink) + } + addr := stringToAddress(parts[1]) + if addr == "" || !isPlayerInGame(addr) { + return "Address not found in game. You need to buy keys first to view your stats.\n\n" + } + keys, dividends := GetPlayerInfo(parts[1]) + return RenderPlayer(addr, keys, dividends) + default: + return "404: Invalid path\n\n" + } +} diff --git a/examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno b/examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno new file mode 100644 index 00000000000..8fb4edf386b --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/fomo3d_test.gno @@ -0,0 +1,294 @@ +package fomo3d + +import ( + "std" + "testing" + + "gno.land/p/demo/avl" + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ownable" + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" +) + +// Reset game state +func setupTestGame(t *testing.T) { + gameState = GameState{ + StartBlock: 0, + EndBlock: 0, + LastKeyBlock: 0, + LastBuyer: "", + Jackpot: 0, + KeyPrice: MIN_KEY_PRICE, + TotalKeys: 0, + Ended: true, + CurrentRound: 0, + NextPot: 0, + OwnerFee: 0, + } + players = avl.NewTree() + Ownable = ownable.New() +} + +// Test ownership functionality +func TestOwnership(t *testing.T) { + owner := testutils.TestAddress("owner") + nonOwner := testutils.TestAddress("nonOwner") + + // Set up initial owner + std.TestSetOriginCaller(owner) + std.TestSetOriginPkgAddress(owner) + setupTestGame(t) + + // Transfer ownership to nonOwner first to test ownership functions + std.TestSetOriginCaller(owner) + urequire.NotPanics(t, func() { + Ownable.TransferOwnership(nonOwner) + }) + + // Test fee accumulation + StartGame() + payment := MIN_KEY_PRICE * 10 + std.TestSetOriginCaller(owner) + std.TestSetOriginSend(std.Coins{{"ugnot", payment}}, nil) + std.TestIssueCoins(owner, std.Coins{{"ugnot", payment}}) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", payment}}) + BuyKeys() + + // Verify fee accumulation + _, fees := GetOwnerInfo() + expectedFees := payment * OWNER_FEE_PERCENT / 100 + urequire.Equal(t, expectedFees, fees) + + // Test unauthorized fee claim (using old owner) + std.TestSetOriginCaller(owner) + urequire.PanicsWithMessage(t, "ownable: caller is not owner", ClaimOwnerFee) + + // Test authorized fee claim (using new owner) + std.TestSetOriginCaller(nonOwner) + initialBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(nonOwner) + std.TestIssueCoins(std.CurrentRealm().Address(), std.Coins{{"ugnot", expectedFees}}) + urequire.NotPanics(t, ClaimOwnerFee) + + // Verify fees were claimed + _, feesAfter := GetOwnerInfo() + urequire.Equal(t, int64(0), feesAfter) + + finalBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(nonOwner) + urequire.Equal(t, initialBalance.AmountOf("ugnot")+expectedFees, finalBalance.AmountOf("ugnot")) +} + +// Test full game flow +func TestFullGameFlow(t *testing.T) { + setupTestGame(t) + + player1 := testutils.TestAddress("player1") + player2 := testutils.TestAddress("player2") + player3 := testutils.TestAddress("player3") + + // Test initial state + urequire.Equal(t, int64(0), gameState.CurrentRound) + urequire.Equal(t, MIN_KEY_PRICE, gameState.KeyPrice) + urequire.Equal(t, true, gameState.Ended) + + // Start game + urequire.NotPanics(t, StartGame) + urequire.Equal(t, false, gameState.Ended) + urequire.Equal(t, std.ChainHeight(), gameState.StartBlock) + urequire.Equal(t, int64(1), gameState.CurrentRound) + + t.Run("buying keys", func(t *testing.T) { + // Test insufficient payment + std.TestSetOriginCaller(player1) + std.TestIssueCoins(player1, std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) + urequire.PanicsWithMessage(t, ErrInsufficientPayment.Error(), BuyKeys) + + // Test successful key purchase + payment := MIN_KEY_PRICE * 3 + std.TestSetOriginSend(std.Coins{{"ugnot", payment}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", payment}}) + + currentBlock := std.ChainHeight() + urequire.NotPanics(t, BuyKeys) + + // Verify time extension + _, endBlock, _, _, _, _, _, _, _, _ := GetGameState() + urequire.Equal(t, currentBlock+TIME_EXTENSION, endBlock) + + // Verify player state + keys, dividends := GetPlayerInfo(player1.String()) + + urequire.Equal(t, int64(3), keys) + urequire.Equal(t, int64(0), dividends) + urequire.Equal(t, player1, gameState.LastBuyer) + + // Verify game state + _, endBlock, _, buyer, pot, price, keys, isEnded, nextPot, round := GetGameState() + urequire.Equal(t, player1, buyer) + urequire.Equal(t, int64(3), keys) + urequire.Equal(t, false, isEnded) + + urequire.Equal(t, payment*JACKPOT_PERCENT/100, pot) + + // Verify owner fee + _, ownerFees := GetOwnerInfo() + urequire.Equal(t, payment*OWNER_FEE_PERCENT/100, ownerFees) + }) + + t.Run("dividend distribution and claiming", func(t *testing.T) { + // Player 2 buys keys + std.TestSetOriginCaller(player2) + payment := gameState.KeyPrice * 2 // Buy 2 keys using current keyPrice + std.TestSetOriginSend(std.Coins{{"ugnot", payment}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", payment}}) + urequire.NotPanics(t, BuyKeys) + + // Check player1 received dividends + keys1, dividends1 := GetPlayerInfo(player1.String()) + + urequire.Equal(t, int64(3), keys1) + expectedDividends := payment * DIVIDENDS_PERCENT / 100 * 3 / gameState.TotalKeys + urequire.Equal(t, expectedDividends, dividends1) + + // Test claiming dividends + { + // Player1 claims dividends + std.TestSetOriginCaller(player1) + initialBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(player1) + urequire.NotPanics(t, ClaimDividends) + + // Verify dividends were claimed + _, dividendsAfter := GetPlayerInfo(player1.String()) + urequire.Equal(t, int64(0), dividendsAfter) + + lastBuyerBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(player1) + urequire.Equal(t, initialBalance.AmountOf("ugnot")+expectedDividends, lastBuyerBalance.AmountOf("ugnot")) + } + }) + + t.Run("game ending", func(t *testing.T) { + // Try ending too early + urequire.PanicsWithMessage(t, ErrGameNotInProgress.Error(), EndGame) + + // Skip to end of current time window + currentEndBlock := gameState.EndBlock + std.TestSkipHeights(currentEndBlock - std.ChainHeight() + 1) + + // End game successfully + urequire.NotPanics(t, EndGame) + urequire.Equal(t, true, gameState.Ended) + urequire.Equal(t, int64(1), gameState.CurrentRound) + + // Verify winner received jackpot + lastBuyerBalance := std.NewBanker(std.BankerTypeRealmSend).GetCoins(gameState.LastBuyer) + urequire.Equal(t, gameState.Jackpot, lastBuyerBalance.AmountOf("ugnot")) + + // Verify NFT was minted to winner + balance, err := BalanceOf(gameState.LastBuyer) + urequire.NoError(t, err) + urequire.Equal(t, uint64(1), balance) + + // Check NFT metadata + tokenID := grc721.TokenID("1") + metadata, err := TokenMetadata(tokenID) + + urequire.NoError(t, err) + urequire.Equal(t, "Fomo3D Winner - Round #1", metadata.Name) + }) + + // Test new round + t.Run("new round", func(t *testing.T) { + // Calculate expected next pot from previous round + payment1 := MIN_KEY_PRICE * 3 + // After buying 3 keys, price increased by 3% (1% per key) + secondKeyPrice := MIN_KEY_PRICE + (MIN_KEY_PRICE * 3 / 100) + payment2 := secondKeyPrice * 2 + expectedNextPot := (payment1 * NEXT_ROUND_POT / 100) + (payment2 * NEXT_ROUND_POT / 100) + + // Start new round + urequire.NotPanics(t, StartGame) + urequire.Equal(t, false, gameState.Ended) + urequire.Equal(t, int64(2), gameState.CurrentRound) + + start, end, last, buyer, pot, price, keys, isEnded, nextPot, round := GetGameState() + urequire.Equal(t, int64(2), round) + urequire.Equal(t, expectedNextPot, pot) + urequire.Equal(t, int64(0), nextPot) + }) +} + +// Test individual components +func TestStartGame(t *testing.T) { + setupTestGame(t) + + // Test starting first game + urequire.NotPanics(t, StartGame) + urequire.Equal(t, false, gameState.Ended) + urequire.Equal(t, std.ChainHeight(), gameState.StartBlock) + + // Test cannot start while game in progress + urequire.PanicsWithMessage(t, ErrGameInProgress.Error(), StartGame) +} + +func TestBuyKeys(t *testing.T) { + setupTestGame(t) + StartGame() + + player := testutils.TestAddress("player") + std.TestSetOriginCaller(player) + + // Test invalid coin denomination + std.TestIssueCoins(player, std.Coins{{"invalid", MIN_KEY_PRICE}}) + std.TestSetOriginSend(std.Coins{{"invalid", MIN_KEY_PRICE}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"invalid", MIN_KEY_PRICE}}) + urequire.PanicsWithMessage(t, ErrInvalidPayment.Error(), BuyKeys) + + // Test multiple coin types + std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE}, {"other", 100}}) + urequire.PanicsWithMessage(t, ErrInvalidPayment.Error(), BuyKeys) + + // Test insufficient payment + std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE - 1}}) + urequire.PanicsWithMessage(t, ErrInsufficientPayment.Error(), BuyKeys) + + // Test successful purchase + std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) + urequire.NotPanics(t, BuyKeys) +} + +func TestClaimDividends(t *testing.T) { + setupTestGame(t) + StartGame() + + player := testutils.TestAddress("player") + std.TestSetOriginCaller(player) + + // Test claiming with no dividends + urequire.PanicsWithMessage(t, ErrNoDividendsToClaim.Error(), ClaimDividends) + + // Setup player with dividends + std.TestIssueCoins(player, std.Coins{{"ugnot", MIN_KEY_PRICE}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE}}) + BuyKeys() + + // Have another player buy to generate dividends + player2 := testutils.TestAddress("player2") + std.TestSetOriginCaller(player2) + std.TestIssueCoins(player2, std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) + std.TestSetOriginSend(std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}, nil) + std.TestIssueCoins(std.OriginPkgAddress(), std.Coins{{"ugnot", MIN_KEY_PRICE * 2}}) + BuyKeys() + + // Test successful claim + std.TestSetOriginCaller(player) + urequire.NotPanics(t, ClaimDividends) +} diff --git a/examples/gno.land/r/stefann/fomo3d/gno.mod b/examples/gno.land/r/stefann/fomo3d/gno.mod new file mode 100644 index 00000000000..1b4e630a285 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/gno.mod @@ -0,0 +1 @@ +module gno.land/r/stefann/fomo3d diff --git a/examples/gno.land/r/stefann/fomo3d/nft.gno b/examples/gno.land/r/stefann/fomo3d/nft.gno new file mode 100644 index 00000000000..adea2fee795 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/nft.gno @@ -0,0 +1,88 @@ +package fomo3d + +import ( + "std" + "strconv" + + "gno.land/p/demo/grc/grc721" +) + +var ( + fomo3dNFT = grc721.NewNFTWithMetadata("Fomo3D Winner", "FOMO") +) + +// Public getters + +func Name() string { + return fomo3dNFT.Name() +} + +func Symbol() string { + return fomo3dNFT.Symbol() +} + +func BalanceOf(owner std.Address) (uint64, error) { + return fomo3dNFT.BalanceOf(owner) +} + +func OwnerOf(tokenID grc721.TokenID) (std.Address, error) { + return fomo3dNFT.OwnerOf(tokenID) +} + +func TokenMetadata(tokenID grc721.TokenID) (grc721.Metadata, error) { + return fomo3dNFT.TokenMetadata(tokenID) +} + +// Transfer and approval methods + +func TransferFrom(from, to std.Address, tokenID grc721.TokenID) error { + return fomo3dNFT.TransferFrom(from, to, tokenID) +} + +func SafeTransferFrom(from, to std.Address, tokenID grc721.TokenID) error { + return fomo3dNFT.SafeTransferFrom(from, to, tokenID) +} + +func Approve(approved std.Address, tokenID grc721.TokenID) error { + return fomo3dNFT.Approve(approved, tokenID) +} + +func GetApproved(tokenID grc721.TokenID) (std.Address, error) { + return fomo3dNFT.GetApproved(tokenID) +} + +func SetApprovalForAll(operator std.Address, approved bool) error { + return fomo3dNFT.SetApprovalForAll(operator, approved) +} + +func IsApprovedForAll(owner, operator std.Address) bool { + return fomo3dNFT.IsApprovedForAll(owner, operator) +} + +// Mints a new NFT for the round winner +func mintRoundWinnerNFT(winner std.Address, roundNumber int64) error { + if winner == "" { + return ErrZeroAddress + } + + roundStr := strconv.FormatInt(roundNumber, 10) + tokenID := grc721.TokenID(roundStr) + + // Create metadata + metadata := grc721.Metadata{ + Name: "Fomo3D Winner - Round #" + roundStr, + Description: "Winner of Fomo3D round #" + roundStr, + Image: "https://ipfs.io/ipfs/bafybeidayyli6bpewkhgtwqpgubmo77kmgjn4r5zq2i7usoyadcmvynhhq", + ExternalURL: "https://gno.land/r/stefann/fomo3d:round/" + roundStr, // TODO: Add this render in main realm that shows details of specific round + Attributes: []grc721.Trait{}, + BackgroundColor: "2D2D2D", // Dark theme background + } + + if err := fomo3dNFT.Mint(winner, tokenID); err != nil { + return err + } + + fomo3dNFT.SetTokenMetadata(tokenID, metadata) + + return nil +} diff --git a/examples/gno.land/r/stefann/fomo3d/render.gno b/examples/gno.land/r/stefann/fomo3d/render.gno new file mode 100644 index 00000000000..55f20220aa9 --- /dev/null +++ b/examples/gno.land/r/stefann/fomo3d/render.gno @@ -0,0 +1,138 @@ +package fomo3d + +import ( + "std" + "strconv" + "strings" + + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ufmt" + + "gno.land/r/sys/users" +) + +// RenderHome renders the main game state +func RenderHome() string { + var builder strings.Builder + builder.WriteString("# FOMO3D - The Ultimate Game of Greed\n\n") + + // About section + builder.WriteString("## About the Game\n\n") + builder.WriteString("FOMO3D is a game that combines elements of lottery and investment mechanics. ") + builder.WriteString("Players purchase keys using GNOT tokens, where each key purchase:\n\n") + builder.WriteString("* Extends the game timer\n") + builder.WriteString("* Increases the key price by 1%\n") + builder.WriteString("* Makes you the potential winner of the jackpot\n") + builder.WriteString("* Distributes dividends to all key holders\n\n") + builder.WriteString("## How to Win\n\n") + builder.WriteString("* Be the last person to buy a key before the timer expires!\n\n") + builder.WriteString("**Rewards Distribution:**\n") + builder.WriteString("* 47% goes to the jackpot (for the winner)\n") + builder.WriteString("* 28% distributed as dividends to all key holders\n") + builder.WriteString("* 20% goes to next round's starting pot\n") + builder.WriteString("* 5% development fee for continuous improvement\n\n") + + // Play Game section + builder.WriteString("## How to Play\n\n") + builder.WriteString(ufmt.Sprintf("1. **Buy Keys** - Send GNOT to this realm with function [`BuyKeys()`](%s)\n", gameState.BuyKeysLink)) + builder.WriteString(ufmt.Sprintf("2. **Collect Dividends** - Call [`ClaimDividends()`](%s) to collect your earnings\n", gameState.ClaimDividendsLink)) + builder.WriteString("3. **Check Your Stats** - Append `:player/` followed by your address or namespace to the current URL to view your keys and dividends\n") + if gameState.Ended { + builder.WriteString(ufmt.Sprintf("4. **Start New Round** - Call [`StartGame()`](%s) to begin a new round\n", gameState.StartGameLink)) + } + builder.WriteString("\n") + + // Game Status section + builder.WriteString("## Game Status\n\n") + if gameState.StartBlock == 0 { + builder.WriteString("🔴 Game has not started yet.\n\n") + } else { + if gameState.Ended { + builder.WriteString("🔴 **Game Status:** Ended\n") + builder.WriteString(ufmt.Sprintf("🏆 **Winner:** %s\n\n", gameState.LastBuyer)) + } else { + builder.WriteString("🟢 **Game Status:** Active\n\n") + builder.WriteString(ufmt.Sprintf("🔄 **Round:** %d\n\n", gameState.CurrentRound)) + builder.WriteString(ufmt.Sprintf("⏱️ **Time Remaining:** %d blocks\n\n", gameState.EndBlock-std.ChainHeight())) + } + builder.WriteString(ufmt.Sprintf("💰 **Jackpot:** %d ugnot\n\n", gameState.Jackpot)) + builder.WriteString(ufmt.Sprintf("🔑 **Key Price:** %d ugnot\n\n", gameState.KeyPrice)) + builder.WriteString(ufmt.Sprintf("📊 **Total Keys:** %d\n\n", gameState.TotalKeys)) + builder.WriteString(ufmt.Sprintf("👤 **Last Buyer:** %s\n\n", getDisplayName(gameState.LastBuyer))) + builder.WriteString(ufmt.Sprintf("🎮 **Next Round Pot:** %d ugnot\n\n", gameState.NextPot)) + } + + // Separator before less important sections + builder.WriteString("---\n\n") + + // Vote For Me section + builder.WriteString("### Vote For Us! 🗳️\n\n") + builder.WriteString("If you enjoy playing FOMO3D, please consider upvoting this game in the [Hall of Realms](https://gno.land/r/leon/hof)!\n\n") + builder.WriteString("Your support helps more players discover the game and grow our community! 🚀\n\n") + + // Report Bug section + builder.WriteString("### Report a Bug 🪲\n\n") + builder.WriteString("Something unusual happened? Help us improve the game by reporting bugs!\n") + builder.WriteString("[Visit our GitHub repository](https://github.com/gnolang/gno/issues)\n\n") + builder.WriteString("Please include:\n") + builder.WriteString("* Detailed description of what happened\n") + builder.WriteString("* Transaction hash (if applicable)\n") + builder.WriteString("* Your address\n") + builder.WriteString("* Current round number\n") + + return builder.String() +} + +// RenderPlayer renders specific player information +func RenderPlayer(addr std.Address, keys int64, dividends int64) string { + var builder strings.Builder + displayName := getDisplayName(addr) + builder.WriteString(ufmt.Sprintf("# Player Stats: %s\n\n", displayName)) + builder.WriteString("## Your Holdings\n\n") + builder.WriteString(ufmt.Sprintf("🔑 **Keys Owned:** %d\n\n", keys)) + builder.WriteString(ufmt.Sprintf("💰 **Unclaimed Dividends:** %d ugnot\n\n", dividends)) + + // Check if player has any NFTs + nftBalance, err := BalanceOf(addr) + if err == nil && nftBalance > 0 { + builder.WriteString("## Your Victory NFTs 🏆\n\n") + + // Iterate through all rounds up to current round to find player's NFTs + for i := int64(1); i <= gameState.CurrentRound; i++ { + tokenID := grc721.TokenID(strconv.FormatInt(i, 10)) + owner, err := OwnerOf(tokenID) + if err == nil && owner == addr { + metadata, err := TokenMetadata(tokenID) + if err == nil { + builder.WriteString(ufmt.Sprintf("### Round #%d Winner\n", i)) + builder.WriteString(ufmt.Sprintf("![NFT](%s)\n\n", metadata.Image)) + builder.WriteString("---\n\n") + } + } + } + } + + builder.WriteString("## Actions\n\n") + builder.WriteString(ufmt.Sprintf("* To buy more keys, send GNOT to this realm with [`BuyKeys()`](%s)\n", gameState.BuyKeysLink)) + if dividends > 0 { + builder.WriteString("* You have unclaimed dividends! Call `ClaimDividends()` to collect them\n") + } + + return builder.String() +} + +// Helper to get display name - just returns namespace if exists, otherwise address +func getDisplayName(addr std.Address) string { + if user := users.ResolveAddress(addr); user != nil { + return user.Name() + } + return addr.String() +} + +// UpdateFunctionLinks updates the links for game functions +func UpdateFunctionLinks(buyKeysLink string, claimDividendsLink string, startGameLink string) { + Ownable.AssertCallerIsOwner() + gameState.BuyKeysLink = buyKeysLink + gameState.ClaimDividendsLink = claimDividendsLink + gameState.StartGameLink = startGameLink +} diff --git a/examples/gno.land/r/stefann/home/home.gno b/examples/gno.land/r/stefann/home/home.gno index f54721ce37c..2a0fbae2faf 100644 --- a/examples/gno.land/r/stefann/home/home.gno +++ b/examples/gno.land/r/stefann/home/home.gno @@ -8,8 +8,8 @@ import ( "gno.land/p/demo/avl" "gno.land/p/demo/ownable" "gno.land/p/demo/ufmt" - "gno.land/r/demo/users" "gno.land/r/leon/hof" + "gno.land/r/sys/users" "gno.land/r/stefann/registry" ) @@ -127,8 +127,8 @@ func UpdateMaxSponsors(newMax int) { } func Donate() { - address := std.GetOrigCaller() - amount := std.GetOrigSend() + address := std.OriginCaller() + amount := std.OriginSend() if amount.AmountOf("ugnot") == 0 { panic("Donation must include GNOT") @@ -146,9 +146,9 @@ func Donate() { travel.currentCityIndex++ sponsorship.DonationsCount++ - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) ownerAddr := registry.MainAddr() - banker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr())) + banker.SendCoins(std.CurrentRealm().Address(), ownerAddr, banker.GetCoins(std.CurrentRealm().Address())) } type SponsorSlice []Sponsor @@ -168,7 +168,7 @@ func (s SponsorSlice) Swap(i, j int) { func GetTopSponsors() []Sponsor { var sponsorSlice SponsorSlice - sponsorship.sponsors.Iterate("", "", func(key string, value interface{}) bool { + sponsorship.sponsors.Iterate("", "", func(key string, value any) bool { addr := std.Address(key) amount := value.(std.Coins) sponsorSlice = append(sponsorSlice, Sponsor{Address: addr, Amount: amount}) @@ -181,7 +181,7 @@ func GetTopSponsors() []Sponsor { func GetTotalDonations() int { total := 0 - sponsorship.sponsors.Iterate("", "", func(key string, value interface{}) bool { + sponsorship.sponsors.Iterate("", "", func(key string, value any) bool { total += int(value.(std.Coins).AmountOf("ugnot")) return false }) @@ -231,8 +231,8 @@ func formatAddress(address string) string { } func getDisplayName(addr std.Address) string { - if user := users.GetUserByAddress(addr); user != nil { - return user.Name + if user := users.ResolveAddress(addr); user != nil { + return user.Name() } return formatAddress(addr.String()) } diff --git a/examples/gno.land/r/stefann/home/home_test.gno b/examples/gno.land/r/stefann/home/home_test.gno index b8ea88670a6..8fa8337dab4 100644 --- a/examples/gno.land/r/stefann/home/home_test.gno +++ b/examples/gno.land/r/stefann/home/home_test.gno @@ -11,7 +11,7 @@ import ( func TestUpdateAboutMe(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) profile.aboutMe = []string{} @@ -32,7 +32,7 @@ func TestUpdateAboutMe(t *testing.T) { func TestUpdateCities(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) travel.cities = []City{} @@ -54,7 +54,7 @@ func TestUpdateCities(t *testing.T) { func TestUpdateJarLink(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) travel.jarLink = "" @@ -67,7 +67,7 @@ func TestUpdateJarLink(t *testing.T) { func TestUpdateMaxSponsors(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) sponsorship.maxSponsors = 0 @@ -87,7 +87,7 @@ func TestUpdateMaxSponsors(t *testing.T) { func TestAddCities(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) travel.cities = []City{} @@ -115,7 +115,7 @@ func TestAddCities(t *testing.T) { func TestAddAboutMeRows(t *testing.T) { var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8") - std.TestSetOrigCaller(owner) + std.TestSetOriginCaller(owner) profile.aboutMe = []string{} @@ -140,7 +140,7 @@ func TestAddAboutMeRows(t *testing.T) { func TestDonate(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) sponsorship.sponsors = avl.NewTree() sponsorship.DonationsCount = 0 @@ -148,7 +148,7 @@ func TestDonate(t *testing.T) { travel.currentCityIndex = 0 coinsSent := std.NewCoins(std.NewCoin("ugnot", 500)) - std.TestSetOrigSend(coinsSent, std.NewCoins()) + std.TestSetOriginSend(coinsSent, std.NewCoins()) Donate() existingAmount, exists := sponsorship.sponsors.Get(string(user)) @@ -173,7 +173,7 @@ func TestDonate(t *testing.T) { } coinsSent = std.NewCoins(std.NewCoin("ugnot", 300)) - std.TestSetOrigSend(coinsSent, std.NewCoins()) + std.TestSetOriginSend(coinsSent, std.NewCoins()) Donate() existingAmount, exists = sponsorship.sponsors.Get(string(user)) @@ -196,7 +196,7 @@ func TestDonate(t *testing.T) { func TestGetTopSponsors(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) sponsorship.sponsors = avl.NewTree() sponsorship.sponsorsCount = 0 @@ -227,7 +227,7 @@ func TestGetTopSponsors(t *testing.T) { func TestGetTotalDonations(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) sponsorship.sponsors = avl.NewTree() sponsorship.sponsorsCount = 0 diff --git a/examples/gno.land/r/sunspirit/home/gno.mod b/examples/gno.land/r/sunspirit/home/gno.mod new file mode 100644 index 00000000000..2aea0280fff --- /dev/null +++ b/examples/gno.land/r/sunspirit/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/sunspirit/home diff --git a/examples/gno.land/r/sunspirit/home/home.gno b/examples/gno.land/r/sunspirit/home/home.gno new file mode 100644 index 00000000000..fbf9709e8d4 --- /dev/null +++ b/examples/gno.land/r/sunspirit/home/home.gno @@ -0,0 +1,34 @@ +package home + +import ( + "strings" + + "gno.land/p/demo/ufmt" + "gno.land/p/sunspirit/md" +) + +func Render(path string) string { + var sb strings.Builder + + sb.WriteString(md.H1("Sunspirit's Home") + md.LineBreak(1)) + + sb.WriteString(md.Paragraph(ufmt.Sprintf( + "Welcome to Sunspirit’s home! This is where I’ll bring %s to Gno.land, crafted with my experience and creativity.", + md.Italic(md.Bold("simple, useful dapps")), + )) + md.LineBreak(1)) + + sb.WriteString(md.Paragraph(ufmt.Sprintf( + "📚 I’ve created a Markdown rendering library at %s. Feel free to use it for your own projects!", + md.Link("gno.land/p/sunspirit/md", "/p/sunspirit/md"), + )) + md.LineBreak(1)) + + sb.WriteString(md.Paragraph("💬 I’d love to hear your feedback to help improve this library!") + md.LineBreak(1)) + + sb.WriteString(md.Paragraph(ufmt.Sprintf( + "🌐 You can check out a demo of this package in action at %s.", + md.Link("gno.land/r/sunspirit/md", "/r/sunspirit/md"), + )) + md.LineBreak(1)) + sb.WriteString(md.HorizontalRule()) + + return sb.String() +} diff --git a/examples/gno.land/r/sunspirit/md/gno.mod b/examples/gno.land/r/sunspirit/md/gno.mod new file mode 100644 index 00000000000..ff3a7c54d96 --- /dev/null +++ b/examples/gno.land/r/sunspirit/md/gno.mod @@ -0,0 +1 @@ +module gno.land/r/sunspirit/md diff --git a/examples/gno.land/r/sunspirit/md/md.gno b/examples/gno.land/r/sunspirit/md/md.gno new file mode 100644 index 00000000000..8c21ea0215c --- /dev/null +++ b/examples/gno.land/r/sunspirit/md/md.gno @@ -0,0 +1,158 @@ +package md + +import ( + "gno.land/p/sunspirit/md" + "gno.land/p/sunspirit/table" +) + +func Render(path string) string { + title := "A simple, flexible, and easy-to-use library for creating markdown documents in gno.land" + + mdBuilder := md.NewBuilder(). + Add(md.H1(md.Italic(md.Bold(title)))). + + // Bold Text section + Add( + md.H3(md.Bold("1. Bold Text")), + md.Paragraph("To make text bold, use the `md.Bold()` function:"), + md.Bold("This is bold text"), + ). + + // Italic Text section + Add( + md.H3(md.Bold("2. Italic Text")), + md.Paragraph("To make text italic, use the `md.Italic()` function:"), + md.Italic("This is italic text"), + ). + + // Strikethrough Text section + Add( + md.H3(md.Bold("3. Strikethrough Text")), + md.Paragraph("To add strikethrough, use the `md.Strikethrough()` function:"), + md.Strikethrough("This text is strikethrough"), + ). + + // Headers section + Add( + md.H3(md.Bold("4. Headers (H1 to H6)")), + md.Paragraph("You can create headers (H1 to H6) using the `md.H1()` to `md.H6()` functions:"), + md.H1("This is a level 1 header"), + md.H2("This is a level 2 header"), + md.H3("This is a level 3 header"), + md.H4("This is a level 4 header"), + md.H5("This is a level 5 header"), + md.H6("This is a level 6 header"), + ). + + // Bullet List section + Add( + md.H3(md.Bold("5. Bullet List")), + md.Paragraph("To create bullet lists, use the `md.BulletList()` function:"), + md.BulletList([]string{"Item 1", "Item 2", "Item 3"}), + ). + + // Ordered List section + Add( + md.H3(md.Bold("6. Ordered List")), + md.Paragraph("To create ordered lists, use the `md.OrderedList()` function:"), + md.OrderedList([]string{"First", "Second", "Third"}), + ). + + // Todo List section + Add( + md.H3(md.Bold("7. Todo List")), + md.Paragraph("You can create a todo list using the `md.TodoList()` function, which supports checkboxes:"), + md.TodoList([]string{"Task 1", "Task 2"}, []bool{true, false}), + ). + + // Blockquote section + Add( + md.H3(md.Bold("8. Blockquote")), + md.Paragraph("To create blockquotes, use the `md.Blockquote()` function:"), + md.Blockquote("This is a blockquote.\nIt can span multiple lines."), + ). + + // Inline Code section + Add( + md.H3(md.Bold("9. Inline Code")), + md.Paragraph("To insert inline code, use the `md.InlineCode()` function:"), + md.InlineCode("fmt.Println() // inline code"), + ). + + // Code Block section + Add( + md.H3(md.Bold("10. Code Block")), + md.Paragraph("For multi-line code blocks, use the `md.CodeBlock()` function:"), + md.CodeBlock("package main\n\nfunc main() {\n\t// Your code here\n}"), + ). + + // Horizontal Rule section + Add( + md.H3(md.Bold("11. Horizontal Rule")), + md.Paragraph("To add a horizontal rule (separator), use the `md.HorizontalRule()` function:"), + md.LineBreak(1), + md.HorizontalRule(), + ). + + // Language-specific Code Block section + Add( + md.H3(md.Bold("12. Language-specific Code Block")), + md.Paragraph("To create language-specific code blocks, use the `md.LanguageCodeBlock()` function:"), + md.LanguageCodeBlock("go", "package main\n\nfunc main() {}"), + ). + + // Hyperlink section + Add( + md.H3(md.Bold("13. Hyperlink")), + md.Paragraph("To create a hyperlink, use the `md.Link()` function:"), + md.Link("Gnoland official docs", "https://docs.gno.land"), + ). + + // Image section + Add( + md.H3(md.Bold("14. Image")), + md.Paragraph("To insert an image, use the `md.Image()` function:"), + md.LineBreak(1), + md.Image("Gnoland Logo", "https://gnolang.github.io/blog/2024-05-21_the-gnome/src/banner.png"), + ). + + // Footnote section + Add( + md.H3(md.Bold("15. Footnote")), + md.Paragraph("To create footnotes, use the `md.Footnote()` function:"), + md.LineBreak(1), + md.Footnote("1", "This is a footnote."), + ). + + // Table section + Add( + md.H3(md.Bold("16. Table")), + md.Paragraph("To create a table, use the `md.Table()` function. Here's an example of a table:"), + ) + + // Create a table using the table package + tb, _ := table.New([]string{"Feature", "Description"}, [][]string{ + {"Bold", "Make text bold using " + md.Bold("double asterisks")}, + {"Italic", "Make text italic using " + md.Italic("single asterisks")}, + {"Strikethrough", "Cross out text using " + md.Strikethrough("double tildes")}, + }) + mdBuilder.Add(md.Table(tb)) + + // Escaping Markdown section + mdBuilder.Add( + md.H3(md.Bold("17. Escaping Markdown")), + md.Paragraph("Sometimes, you need to escape special Markdown characters (like *, _, and `). Use the `md.EscapeMarkdown()` function for this:"), + ) + + // Example of escaping markdown + text := "- Escape special chars like *, _, and ` in markdown" + mdBuilder.Add( + md.H4("Text Without Escape:"), + text, + md.LineBreak(1), + md.H4("Text With Escape:"), + md.EscapeMarkdown(text), + ) + + return mdBuilder.Render(md.LineBreak(1)) +} diff --git a/examples/gno.land/r/sunspirit/md/md_test.gno b/examples/gno.land/r/sunspirit/md/md_test.gno new file mode 100644 index 00000000000..2e1ce9b9931 --- /dev/null +++ b/examples/gno.land/r/sunspirit/md/md_test.gno @@ -0,0 +1,13 @@ +package md + +import ( + "strings" + "testing" +) + +func TestRender(t *testing.T) { + output := Render("") + if !strings.Contains(output, "A simple, flexible, and easy-to-use library for creating markdown documents in gno.land") { + t.Errorf("invalid output") + } +} diff --git a/examples/gno.land/r/sys/names/gno.mod b/examples/gno.land/r/sys/names/gno.mod new file mode 100644 index 00000000000..3cc9b843ed5 --- /dev/null +++ b/examples/gno.land/r/sys/names/gno.mod @@ -0,0 +1 @@ +module gno.land/r/sys/names diff --git a/examples/gno.land/r/sys/names/render.gno b/examples/gno.land/r/sys/names/render.gno new file mode 100644 index 00000000000..769435b7bb8 --- /dev/null +++ b/examples/gno.land/r/sys/names/render.gno @@ -0,0 +1,6 @@ +package names + +func Render(_ string) string { + return `# r/sys/names +System Realm for checking namespace deployment permissions.` +} diff --git a/examples/gno.land/r/sys/names/verifier.gno b/examples/gno.land/r/sys/names/verifier.gno new file mode 100644 index 00000000000..e81adf77475 --- /dev/null +++ b/examples/gno.land/r/sys/names/verifier.gno @@ -0,0 +1,67 @@ +// Package names provides functionality for checking of package deployments +// by users registered in r/sys/users are done to proper namespaces. +package names + +import ( + "std" + "strings" + + "gno.land/p/demo/ownable" + + "gno.land/r/sys/users" +) + +var ( + Ownable = ownable.NewWithAddress("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") // dropped in genesis via Enable + + enabled = false +) + +// IsAuthorizedAddressForNamespace ensures that the given address has ownership of the given name. +// A user's name found in r/sys/users is equivalent to their namespace. +func IsAuthorizedAddressForNamespace(address std.Address, namespace string) bool { + return verifier(enabled, address, namespace) +} + +// Enable enables the namespace check and drops centralized ownership of this realm. +// The namespace check is disabled initially to ease txtar and other testing contexts, +// but this function is meant to be called in the genesis of a chain. +func Enable() { + Ownable.AssertCallerIsOwner() + enabled = true + Ownable.DropOwnership() +} + +func IsEnabled() bool { + return enabled +} + +// verifier checks the store to see that the +// user has properly registered a given name/namespace. +// This function considers as valid an `address` that matches the `namespace` (PA namespaces) +func verifier(enabled bool, address std.Address, namespace string) bool { + if !enabled { + return true // only in pre-genesis cases + } + + if strings.TrimSpace(address.String()) == "" || strings.TrimSpace(namespace) == "" { + return false + } + + // Allow user with their own address as namespace + // This enables pseudo-anon namespaces + // ie gno.land/{p,r}/{ADDRESS}/** + if address.String() == namespace { + return true + } + + // Can be a registered namespace or an alias + userData, _ := users.ResolveName(namespace) + if userData == nil || userData.IsDeleted() { + return false + } + + /// XXX: add check for r/sys/teams down the line + + return userData.Addr() == address +} diff --git a/examples/gno.land/r/sys/names/verifier_test.gno b/examples/gno.land/r/sys/names/verifier_test.gno new file mode 100644 index 00000000000..47e89f67f40 --- /dev/null +++ b/examples/gno.land/r/sys/names/verifier_test.gno @@ -0,0 +1,54 @@ +package names + +import ( + "std" + "testing" + + "gno.land/p/demo/ownable" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" + + "gno.land/r/sys/users" +) + +var alice = testutils.TestAddress("alice") + +func TestDefaultVerifier(t *testing.T) { + // Check disabled, any case is true + uassert.True(t, verifier(false, alice, alice.String())) + uassert.True(t, verifier(false, "", alice.String())) + uassert.True(t, verifier(false, alice, "somerandomusername")) + + // Check enabled + // username + addr mismatch + uassert.False(t, verifier(true, alice, "notregistered")) + // PA namespace check + uassert.True(t, verifier(true, alice, alice.String())) + + // Empty name/address + uassert.False(t, verifier(true, std.Address(""), "")) + + // Register proper username + std.TestSetRealm(std.NewCodeRealm("gno.land/r/gnoland/users/v1")) // authorized write + std.TestSetOriginCaller(std.DerivePkgAddr("gno.land/r/gnoland/users/v1")) + urequire.NoError(t, users.RegisterUser("alice", alice)) + + // Proper namespace + uassert.True(t, verifier(true, alice, "alice")) +} + +func TestEnable(t *testing.T) { + std.TestSetRealm(std.NewUserRealm("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj")) + std.TestSetOriginCaller("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + uassert.NotPanics(t, func() { + Enable() + }) + + // Confirm enable drops ownerships + uassert.Equal(t, Ownable.Owner().String(), "") + uassert.PanicsWithMessage(t, ownable.ErrUnauthorized.Error(), func() { + Enable() + }) +} diff --git a/examples/gno.land/r/sys/params/params.gno b/examples/gno.land/r/sys/params/params.gno index fa04c90de3f..9a65c7ea319 100644 --- a/examples/gno.land/r/sys/params/params.gno +++ b/examples/gno.land/r/sys/params/params.gno @@ -13,42 +13,80 @@ // // Example usage: // -// executor := params.NewStringPropExecutor("exampleKey", "exampleValue") // // This executor can be used in a governance proposal to set the parameter. +// executor := params.NewStringsPropExecutor("bank", "p", "restricted_denoms") package params import ( "std" + prms "sys/params" "gno.land/p/demo/dao" "gno.land/r/gov/dao/bridge" ) -func NewStringPropExecutor(key string, value string) dao.Executor { - return newPropExecutor(key, func() { std.SetParamString(key, value) }) +// this is only used for emitting events. +func syskey(module, submodule, name string) string { + return module + ":" + submodule + ":" + name } -func NewInt64PropExecutor(key string, value int64) dao.Executor { - return newPropExecutor(key, func() { std.SetParamInt64(key, value) }) +func NewSysParamStringPropExecutor(module, submodule, name string, value string) dao.Executor { + return newPropExecutor( + syskey(module, submodule, name), + func() { prms.SetSysParamString(module, submodule, name, value) }, + ) } -func NewUint64PropExecutor(key string, value uint64) dao.Executor { - return newPropExecutor(key, func() { std.SetParamUint64(key, value) }) +func NewSysParamInt64PropExecutor(module, submodule, name string, value int64) dao.Executor { + return newPropExecutor( + syskey(module, submodule, name), + func() { prms.SetSysParamInt64(module, submodule, name, value) }, + ) } -func NewBoolPropExecutor(key string, value bool) dao.Executor { - return newPropExecutor(key, func() { std.SetParamBool(key, value) }) +func NewSysParamUint64PropExecutor(module, submodule, name string, value uint64) dao.Executor { + return newPropExecutor( + syskey(module, submodule, name), + func() { prms.SetSysParamUint64(module, submodule, name, value) }, + ) } -func NewBytesPropExecutor(key string, value []byte) dao.Executor { - return newPropExecutor(key, func() { std.SetParamBytes(key, value) }) +func NewSysParamBoolPropExecutor(module, submodule, name string, value bool) dao.Executor { + return newPropExecutor( + syskey(module, submodule, name), + func() { prms.SetSysParamBool(module, submodule, name, value) }, + ) +} + +func NewSysParamBytesPropExecutor(module, submodule, name string, value []byte) dao.Executor { + return newPropExecutor( + syskey(module, submodule, name), + func() { prms.SetSysParamBytes(module, submodule, name, value) }, + ) +} + +func NewSysParamStringsPropExecutor(module, submodule, name string, value []string) dao.Executor { + return newPropExecutor( + syskey(module, submodule, name), + func() { prms.SetSysParamStrings(module, submodule, name, value) }, + ) } func newPropExecutor(key string, fn func()) dao.Executor { callback := func() error { fn() - std.Emit("set", "k", key) + std.Emit("set", "key", key) // TODO document, make const, make consistent. 'k'?? return nil } return bridge.GovDAO().NewGovDAOExecutor(callback) } +func propose(exec dao.Executor, title, desc string) uint64 { + // The executor's callback function is executed only after the proposal + // has been voted on and approved by the GovDAO. + prop := dao.ProposalRequest{ + Title: title, + Description: desc, + Executor: exec, + } + return bridge.GovDAO().Propose(prop) +} diff --git a/examples/gno.land/r/sys/params/params_test.gno b/examples/gno.land/r/sys/params/params_test.gno index eaa1ad039d3..e7c90632d0e 100644 --- a/examples/gno.land/r/sys/params/params_test.gno +++ b/examples/gno.land/r/sys/params/params_test.gno @@ -1,6 +1,10 @@ package params -import "testing" +import ( + "testing" + + _ "gno.land/r/gov/dao/init" // so that loader.init is executed +) // Testing this package is limited because it only contains an `std.Set` method // without a corresponding `std.Get` method. For comprehensive testing, refer to @@ -8,7 +12,7 @@ import "testing" // propX_filetest.gno files. func TestNewStringPropExecutor(t *testing.T) { - executor := NewStringPropExecutor("foo", "bar") + executor := NewSysParamStringPropExecutor("foo", "bar", "baz", "qux") if executor == nil { t.Errorf("executor shouldn't be nil") } diff --git a/examples/gno.land/r/sys/params/unlock.gno b/examples/gno.land/r/sys/params/unlock.gno new file mode 100644 index 00000000000..4c2d02f2588 --- /dev/null +++ b/examples/gno.land/r/sys/params/unlock.gno @@ -0,0 +1,18 @@ +package params + +const ( + bankModulePrefix = "bank" + restrictedDenomsKey = "restricted_denoms" + unlockTransferTitle = "Proposal to unlock the transfer of ugnot." + lockTransferTitle = "Proposal to lock the transfer of ugnot." +) + +func ProposeUnlockTransfer() uint64 { + exe := NewSysParamStringsPropExecutor(bankModulePrefix, "p", restrictedDenomsKey, []string{}) + return propose(exe, unlockTransferTitle, "") +} + +func ProposeLockTransfer() uint64 { + exe := NewSysParamStringsPropExecutor(bankModulePrefix, "p", restrictedDenomsKey, []string{"ugnot"}) + return propose(exe, lockTransferTitle, "") +} diff --git a/examples/gno.land/r/sys/params/unlock_test.gno b/examples/gno.land/r/sys/params/unlock_test.gno new file mode 100644 index 00000000000..01b5ab2e311 --- /dev/null +++ b/examples/gno.land/r/sys/params/unlock_test.gno @@ -0,0 +1,54 @@ +package params + +import ( + "testing" + + "gno.land/p/demo/dao" + "gno.land/p/demo/simpledao" + "gno.land/p/demo/urequire" + "gno.land/r/gov/dao/bridge" + + // loads the latest DAO implementation in the bridge. + _ "gno.land/r/gov/dao/init" +) + +func TestProUnlockTransfer(t *testing.T) { + govdao := bridge.GovDAO() + id := ProposeUnlockTransfer() + p, err := govdao.GetPropStore().ProposalByID(id) + urequire.NoError(t, err) + urequire.Equal(t, unlockTransferTitle, p.Title()) +} + +func TestFailUnlockTransfer(t *testing.T) { + govdao := bridge.GovDAO() + id := ProposeUnlockTransfer() + urequire.PanicsWithMessage( + t, + simpledao.ErrProposalNotAccepted.Error(), + func() { + govdao.ExecuteProposal(id) + }, + ) +} + +func TestExeUnlockTransfer(t *testing.T) { + govdao := bridge.GovDAO() + id := ProposeUnlockTransfer() + p, err := govdao.GetPropStore().ProposalByID(id) + urequire.NoError(t, err) + urequire.True(t, dao.Active == p.Status()) + + govdao.VoteOnProposal(id, dao.YesVote) + + urequire.True(t, dao.Accepted == p.Status()) + + urequire.NotPanics( + t, + func() { + govdao.ExecuteProposal(id) + }, + ) + + urequire.True(t, dao.ExecutionSuccessful == p.Status()) +} diff --git a/examples/gno.land/r/sys/users/admin.gno b/examples/gno.land/r/sys/users/admin.gno new file mode 100644 index 00000000000..15183a41856 --- /dev/null +++ b/examples/gno.land/r/sys/users/admin.gno @@ -0,0 +1,62 @@ +package users + +import ( + "std" + + "gno.land/p/demo/dao" + "gno.land/p/moul/addrset" + + "gno.land/r/gov/dao/bridge" +) + +const gusersv1 = "gno.land/r/gnoland/users/v1" // preregistered with store write perms + +var controllers = addrset.Set{} // caller whitelist + +func init() { + controllers.Add(std.DerivePkgAddr(gusersv1)) // initially whitelisted +} + +// ProposeNewController allows GovDAO to add a whitelisted caller +func ProposeNewController(addr std.Address) dao.Executor { + cb := func() error { + return addToWhitelist(addr) + } + + return bridge.GovDAO().NewGovDAOExecutor(cb) +} + +// ProposeControllerRemoval allows GovDAO to add a whitelisted caller +func ProposeControllerRemoval(addr std.Address) dao.Executor { + cb := func() error { + return deleteFromwhitelist(addr) + } + + return bridge.GovDAO().NewGovDAOExecutor(cb) +} + +// Helpers + +func deleteFromwhitelist(addr std.Address) error { + if !controllers.Has(addr) { + return ErrNotWhitelisted + } + + if ok := controllers.Remove(addr); !ok { + panic("failed to remove address from whitelist") + } + + return nil +} + +func addToWhitelist(newCaller std.Address) error { + if !newCaller.IsValid() { + return ErrInvalidAddress + } + + if !controllers.Add(newCaller) { + return ErrAlreadyWhitelisted + } + + return nil +} diff --git a/examples/gno.land/r/sys/users/errors.gno b/examples/gno.land/r/sys/users/errors.gno new file mode 100644 index 00000000000..01cd548b115 --- /dev/null +++ b/examples/gno.land/r/sys/users/errors.gno @@ -0,0 +1,22 @@ +package users + +import "errors" + +const prefix = "r/sys/users: " + +var ( + ErrNotWhitelisted = errors.New(prefix + "does not exist in whitelist") + ErrAlreadyWhitelisted = errors.New(prefix + "already whitelisted") + + ErrNameTaken = errors.New(prefix + "name/Alias already taken") + ErrInvalidAddress = errors.New(prefix + "invalid address") + + ErrEmptyUsername = errors.New(prefix + "empty username provided") + ErrNameLikeAddress = errors.New(prefix + "username resembles a gno.land address") + ErrInvalidUsername = errors.New(prefix + "username must match ^[a-zA-Z0-9_]{1,64}$") + + ErrAlreadyHasName = errors.New(prefix + "username for this address already registered - try creating an Alias") + ErrDeletedUser = errors.New(prefix + "cannot register a new username after deleting") + + ErrUserNotExistOrDeleted = errors.New(prefix + "this user does not exist or was deleted") +) diff --git a/examples/gno.land/r/sys/users/render.gno b/examples/gno.land/r/sys/users/render.gno new file mode 100644 index 00000000000..6709ba5097c --- /dev/null +++ b/examples/gno.land/r/sys/users/render.gno @@ -0,0 +1,16 @@ +package users + +import "gno.land/p/demo/ufmt" + +func Render(_ string) string { + out := "# r/sys/users\n\n" + + out += "`r/sys/users` is a system realm for managing user registrations.\n\n" + out += "Users should use [`gno.land/r/gnoland/users`](/r/gnoland/users) implementations to register their usernames.\n\n" + out += "---\n\n" + + out += "## Stats\n\n" + out += ufmt.Sprintf("Total unique addresses registered: **%d**\n\n", addressStore.Size()) + out += ufmt.Sprintf("Total unique names registered: **%d**\n\n", nameStore.Size()) + return out +} diff --git a/examples/gno.land/r/sys/users/store.gno b/examples/gno.land/r/sys/users/store.gno new file mode 100644 index 00000000000..8c4c478e049 --- /dev/null +++ b/examples/gno.land/r/sys/users/store.gno @@ -0,0 +1,173 @@ +package users + +import ( + "regexp" + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" +) + +var ( + nameStore = avl.NewTree() // name/aliases > *UserData + addressStore = avl.NewTree() // address > *UserData + + reAddressLookalike = regexp.MustCompile(`^g1[a-z0-9]{20,38}$`) + reAlphanum = regexp.MustCompile(`^[a-zA-Z0-9_]{1,64}$`) +) + +const ( + RegisterUserEvent = "Registered" + UpdateNameEvent = "Updated" + DeleteUserEvent = "Deleted" +) + +type UserData struct { + addr std.Address + username string // contains the latest name of a user + deleted bool +} + +func (u UserData) Name() string { + return u.username +} + +func (u UserData) Addr() std.Address { + return u.addr +} + +func (u UserData) IsDeleted() bool { + return u.deleted +} + +// RenderLink provides a render link to the user page on gnoweb +// `linkText` is optional +func (u UserData) RenderLink(linkText string) string { + // TODO switch to /u/username once the gnoweb page is ready. + if linkText == "" { + return ufmt.Sprintf("[@%s](/r/gnoland/users/v1:%s)", u.username, u.username) + } + + return ufmt.Sprintf("[%s](/r/gnoland/users/v1:%s)", linkText, u.username) +} + +// RegisterUser adds a new user to the system. +func RegisterUser(name string, address std.Address) error { + // Validate caller + if !controllers.Has(std.PreviousRealm().Address()) { + return ErrNotWhitelisted + } + + // Validate name + if err := validateName(name); err != nil { + return err + } + + // Validate address + if !address.IsValid() { + return ErrInvalidAddress + } + + // Check if name is taken + if nameStore.Has(name) { + return ErrNameTaken + } + + raw, ok := addressStore.Get(address.String()) + if ok { + // Cannot re-register after deletion + if raw.(*UserData).IsDeleted() { + return ErrDeletedUser + } + + // For a second name, use UpdateName + return ErrAlreadyHasName + } + + // Create UserData + data := &UserData{ + addr: address, + username: name, + deleted: false, + } + + // Set corresponding stores + nameStore.Set(name, data) + addressStore.Set(address.String(), data) + + std.Emit(RegisterUserEvent, + "name", name, + "address", address.String(), + ) + return nil +} + +// UpdateName adds a name that is associated with a specific address +// All previous names are preserved and resolvable. +// The new name is the default value returned for address lookups. +func (u *UserData) UpdateName(newName string) error { + if u == nil { // either doesnt exists or was deleted + return ErrUserNotExistOrDeleted + } + + // Validate caller + if !controllers.Has(std.PreviousRealm().Address()) { + return ErrNotWhitelisted + } + + // Validate name + if err := validateName(newName); err != nil { + return err + } + + // Check if the requested Alias is already taken + if nameStore.Has(newName) { + return ErrNameTaken + } + + u.username = newName + nameStore.Set(newName, u) + + std.Emit(UpdateNameEvent, + "alias", newName, + "address", u.addr.String(), + ) + return nil +} + +// Delete marks a user and all their aliases as deleted. +func (u *UserData) Delete() error { + if u == nil { + return ErrUserNotExistOrDeleted + } + + // Validate caller + if !controllers.Has(std.PreviousRealm().Address()) { + return ErrNotWhitelisted + } + + u.deleted = true + + std.Emit(DeleteUserEvent, "address", u.addr.String()) + return nil +} + +// Validate validates username and address passed in +// Most of the validation is done in the controllers +// This provides more flexibility down the line +func validateName(username string) error { + if username == "" { + return ErrEmptyUsername + } + + if !reAlphanum.MatchString(username) { + return ErrInvalidUsername + } + + // Check if the username can be decoded or looks like a valid address + if std.Address(username).IsValid() || reAddressLookalike.MatchString(username) { + return ErrNameLikeAddress + } + + return nil +} diff --git a/examples/gno.land/r/sys/users/store_test.gno b/examples/gno.land/r/sys/users/store_test.gno new file mode 100644 index 00000000000..e93ebb678b1 --- /dev/null +++ b/examples/gno.land/r/sys/users/store_test.gno @@ -0,0 +1,205 @@ +package users + +import ( + "std" + "testing" + + "gno.land/p/demo/avl" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +var ( + alice = "alice" + aliceAddr = testutils.TestAddress(alice) + bob = "bob" + bobAddr = testutils.TestAddress(bob) + + whitelistedCallerAddr = std.DerivePkgAddr(gusersv1) +) + +func TestRegister(t *testing.T) { + std.TestSetOriginCaller(whitelistedCallerAddr) + + t.Run("valid_registration", func(t *testing.T) { + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + + res, isLatest := ResolveName(alice) + uassert.Equal(t, aliceAddr, res.Addr()) + uassert.True(t, isLatest) + + res = ResolveAddress(aliceAddr) + uassert.Equal(t, alice, res.Name()) + }) + + t.Run("invalid_inputs", func(t *testing.T) { + cleanStore(t) + + uassert.ErrorContains(t, RegisterUser("", aliceAddr), ErrEmptyUsername.Error()) + uassert.ErrorContains(t, RegisterUser(alice, ""), ErrInvalidAddress.Error()) + uassert.ErrorContains(t, RegisterUser(alice, "invalidaddress"), ErrInvalidAddress.Error()) + + uassert.ErrorContains(t, RegisterUser("username with a space", aliceAddr), ErrInvalidUsername.Error()) + uassert.ErrorContains(t, + RegisterUser("verylongusernameverylongusernameverylongusernameverylongusername1", aliceAddr), + ErrInvalidUsername.Error()) + uassert.ErrorContains(t, RegisterUser("namewith^&()", aliceAddr), ErrInvalidUsername.Error()) + }) + + t.Run("addr_already_registered", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + + // Try registering again + uassert.ErrorContains(t, RegisterUser("othername", aliceAddr), ErrAlreadyHasName.Error()) + }) + + t.Run("name_taken", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + + // Try registering alice's name with bob's address + uassert.ErrorContains(t, RegisterUser(alice, bobAddr), ErrNameTaken.Error()) + }) + + t.Run("user_deleted", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + data := ResolveAddress(aliceAddr) + urequire.NoError(t, data.Delete()) + + // Try re-registering after deletion + uassert.ErrorContains(t, RegisterUser("newname", aliceAddr), ErrDeletedUser.Error()) + }) + + t.Run("address_lookalike", func(t *testing.T) { + cleanStore(t) + + // Address as username + uassert.ErrorContains(t, RegisterUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", aliceAddr), ErrNameLikeAddress.Error()) + // Beginning of address as username + uassert.ErrorContains(t, RegisterUser("g1jg8mtutu9khhfwc4nxmu", aliceAddr), ErrNameLikeAddress.Error()) + uassert.NoError(t, RegisterUser("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5longerthananaddress", aliceAddr)) + }) +} + +func TestUpdateName(t *testing.T) { + std.TestSetOriginCaller(whitelistedCallerAddr) + t.Run("valid_direct_alias", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + data := ResolveAddress(aliceAddr) + uassert.NoError(t, data.UpdateName("alice1")) + }) + + t.Run("valid_double_alias", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + data := ResolveAddress(aliceAddr) + uassert.NoError(t, data.UpdateName("alice2")) + uassert.NoError(t, data.UpdateName("alice3")) + uassert.Equal(t, ResolveAddress(aliceAddr).username, "alice3") + }) + + t.Run("name_taken", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + + data := ResolveAddress(aliceAddr) + uassert.Error(t, data.UpdateName(alice), ErrNameTaken.Error()) + }) + + t.Run("alias_before_name", func(t *testing.T) { + cleanStore(t) + data := ResolveAddress(aliceAddr) // not registered + + uassert.ErrorContains(t, data.UpdateName(alice), ErrUserNotExistOrDeleted.Error()) + }) + + t.Run("alias_after_delete", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + data := ResolveAddress(aliceAddr) + urequire.NoError(t, data.Delete()) + + data = ResolveAddress(aliceAddr) + uassert.ErrorContains(t, data.UpdateName("newalice"), ErrUserNotExistOrDeleted.Error()) + }) + + t.Run("invalid_inputs", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + data := ResolveAddress(aliceAddr) + + uassert.ErrorContains(t, data.UpdateName(""), ErrEmptyUsername.Error()) + uassert.ErrorContains(t, data.UpdateName("username with a space"), ErrInvalidUsername.Error()) + uassert.ErrorContains(t, + data.UpdateName("verylongusernameverylongusernameverylongusernameverylongusername1"), + ErrInvalidUsername.Error()) + uassert.ErrorContains(t, data.UpdateName("namewith^&()"), ErrInvalidUsername.Error()) + }) + + t.Run("address_lookalike", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + data := ResolveAddress(aliceAddr) + + // Address as username + uassert.ErrorContains(t, data.UpdateName("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), ErrNameLikeAddress.Error()) + // Beginning of address as username + uassert.ErrorContains(t, data.UpdateName("g1jg8mtutu9khhfwc4nxmu"), ErrNameLikeAddress.Error()) + uassert.NoError(t, data.UpdateName("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5longerthananaddress")) + }) +} + +func TestDelete(t *testing.T) { + std.TestSetOriginCaller(whitelistedCallerAddr) + + t.Run("non_existent_user", func(t *testing.T) { + cleanStore(t) + + data := ResolveAddress(testutils.TestAddress("unregistered")) + uassert.ErrorContains(t, data.Delete(), ErrUserNotExistOrDeleted.Error()) + }) + + t.Run("double_delete", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + data := ResolveAddress(aliceAddr) + urequire.NoError(t, data.Delete()) + data = ResolveAddress(aliceAddr) + uassert.ErrorContains(t, data.Delete(), ErrUserNotExistOrDeleted.Error()) + }) + + t.Run("valid_delete", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + data := ResolveAddress(aliceAddr) + uassert.NoError(t, data.Delete()) + + resolved1, _ := ResolveName(alice) + uassert.Equal(t, nil, resolved1) + uassert.Equal(t, nil, ResolveAddress(aliceAddr)) + }) +} + +// cleanStore should not be needed, as vm store should be reset after each test. +// Reference: https://github.com/gnolang/gno/issues/1982 +func cleanStore(t *testing.T) { + t.Helper() + + nameStore = avl.NewTree() + addressStore = avl.NewTree() +} diff --git a/examples/gno.land/r/sys/users/users.gno b/examples/gno.land/r/sys/users/users.gno new file mode 100644 index 00000000000..4715ffa25e5 --- /dev/null +++ b/examples/gno.land/r/sys/users/users.gno @@ -0,0 +1,56 @@ +package users + +import ( + "std" + + "gno.land/p/demo/avl/rotree" +) + +// ResolveName returns the latest UserData of a specific user by name or alias +func ResolveName(name string) (data *UserData, isCurrent bool) { + raw, ok := nameStore.Get(name) + if !ok { + return nil, false + } + + data = raw.(*UserData) + if data.deleted { + return nil, false + } + + return data, name == data.username +} + +// ResolveAddress returns the latest UserData of a specific user by address +func ResolveAddress(addr std.Address) *UserData { + raw, ok := addressStore.Get(addr.String()) + if !ok { + return nil + } + + data := raw.(*UserData) + if data.deleted { + return nil + } + + return data +} + +// GetReadonlyAddrStore exposes the address store in readonly mode +func GetReadonlyAddrStore() *rotree.ReadOnlyTree { + return rotree.Wrap(addressStore, makeUserDataSafe) +} + +// GetReadOnlyNameStore exposes the name store in readonly mode +func GetReadOnlyNameStore() *rotree.ReadOnlyTree { + return rotree.Wrap(nameStore, makeUserDataSafe) +} + +func makeUserDataSafe(data any) any { + cpy := new(UserData) + *cpy = *(data.(*UserData)) + if cpy.deleted { + return nil + } + return cpy +} diff --git a/examples/gno.land/r/sys/users/users_test.gno b/examples/gno.land/r/sys/users/users_test.gno new file mode 100644 index 00000000000..4a13e568225 --- /dev/null +++ b/examples/gno.land/r/sys/users/users_test.gno @@ -0,0 +1,182 @@ +package users + +import ( + "std" + "strconv" + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +func TestResolveName(t *testing.T) { + std.TestSetOriginCaller(whitelistedCallerAddr) + + t.Run("single_name", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + + res, isLatest := ResolveName(alice) + uassert.Equal(t, aliceAddr, res.Addr()) + uassert.Equal(t, alice, res.Name()) + uassert.True(t, isLatest) + }) + + t.Run("name+Alias", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + data, _ := ResolveName(alice) + urequire.NoError(t, data.UpdateName("alice1")) + + res, isLatest := ResolveName("alice1") + urequire.NotEqual(t, nil, res) + + uassert.Equal(t, aliceAddr, res.Addr()) + uassert.Equal(t, "alice1", res.Name()) + uassert.True(t, isLatest) + }) + + t.Run("multiple_aliases", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + + // RegisterUser and check each Alias + var names []string + names = append(names, alice) + for i := 0; i < 5; i++ { + alias := "alice" + strconv.Itoa(i) + names = append(names, alias) + + data, _ := ResolveName(alice) + urequire.NoError(t, data.UpdateName(alias)) + } + + for _, alias := range names { + res, _ := ResolveName(alias) + urequire.NotEqual(t, nil, res) + + uassert.Equal(t, aliceAddr, res.Addr()) + uassert.Equal(t, "alice4", res.Name()) + } + }) +} + +func TestResolveAddress(t *testing.T) { + std.TestSetOriginCaller(whitelistedCallerAddr) + + t.Run("single_name", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + + res := ResolveAddress(aliceAddr) + + uassert.Equal(t, aliceAddr, res.Addr()) + uassert.Equal(t, alice, res.Name()) + }) + + t.Run("name+Alias", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + data, _ := ResolveName(alice) + urequire.NoError(t, data.UpdateName("alice1")) + + res := ResolveAddress(aliceAddr) + urequire.NotEqual(t, nil, res) + + uassert.Equal(t, aliceAddr, res.Addr()) + uassert.Equal(t, "alice1", res.Name()) + }) + + t.Run("multiple_aliases", func(t *testing.T) { + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + + // RegisterUser and check each Alias + var names []string + names = append(names, alice) + + for i := 0; i < 5; i++ { + alias := "alice" + strconv.Itoa(i) + names = append(names, alias) + data, _ := ResolveName(alice) + urequire.NoError(t, data.UpdateName(alias)) + } + + res := ResolveAddress(aliceAddr) + uassert.Equal(t, aliceAddr, res.Addr()) + uassert.Equal(t, "alice4", res.Name()) + }) +} + +func TestROStores(t *testing.T) { + std.TestSetOriginCaller(whitelistedCallerAddr) + cleanStore(t) + + urequire.NoError(t, RegisterUser(alice, aliceAddr)) + roNS := GetReadOnlyNameStore() + roAS := GetReadonlyAddrStore() + + t.Run("get user data", func(t *testing.T) { + // Name store + aliceDataRaw, ok := roNS.Get(alice) + uassert.True(t, ok) + + roData, ok := aliceDataRaw.(*UserData) + uassert.True(t, ok, "Could not cast data from RO tree to UserData") + + // Try to modify data + roData.Delete() + raw, ok := nameStore.Get(alice) + uassert.False(t, raw.(*UserData).deleted) + + // Addr store + aliceDataRaw, ok = roAS.Get(aliceAddr.String()) + uassert.True(t, ok) + + roData, ok = aliceDataRaw.(*UserData) + uassert.True(t, ok, "Could not cast data from RO tree to UserData") + + // Try to modify data + roData.Delete() + raw, ok = nameStore.Get(alice) + uassert.False(t, raw.(*UserData).deleted) + }) + + t.Run("get deleted data", func(t *testing.T) { + raw, _ := nameStore.Get(alice) + aliceData := raw.(*UserData) + + urequire.NoError(t, aliceData.Delete()) + urequire.True(t, aliceData.IsDeleted()) + + // Should be nil because of makeSafeFn + rawRoData, ok := roNS.Get(alice) + // uassert.False(t, ok) + // XXX: not sure what to do here, as the tree technically has the data so returns ok + // However the data is intercepted and something else (nilin this case) is returned. + // should we handle this somehow? + + uassert.Equal(t, rawRoData, nil) + _, ok = rawRoData.(*UserData) // shouldn't be castable + uassert.False(t, ok) + }) +} + +// TODO Uncomment after gnoweb /u/ page. +//func TestUserRenderLink(t *testing.T) { +// std.TestSetOriginCaller(whitelistedCallerAddr) +// cleanStore(t) +// +// urequire.NoError(t, RegisterUser(alice, aliceAddr)) +// +// data, _ := ResolveName(alice) +// uassert.Equal(t, data.RenderLink(""), ufmt.Sprintf("[@%s](/u/%s)", alice, alice)) +// text := "my link text!" +// uassert.Equal(t, data.RenderLink(text), ufmt.Sprintf("[%s](/u/%s)", text, alice)) +//} diff --git a/examples/gno.land/r/sys/users/verify.gno b/examples/gno.land/r/sys/users/verify.gno deleted file mode 100644 index 71869fda1a1..00000000000 --- a/examples/gno.land/r/sys/users/verify.gno +++ /dev/null @@ -1,83 +0,0 @@ -package users - -import ( - "std" - - "gno.land/p/demo/ownable" - "gno.land/r/demo/users" -) - -const admin = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul - -type VerifyNameFunc func(enabled bool, address std.Address, name string) bool - -var ( - owner = ownable.NewWithAddress(admin) // Package owner - checkFunc = VerifyNameByUser // Checking namespace callback - enabled = false // For now this package is disabled by default -) - -func IsEnabled() bool { return enabled } - -// This method ensures that the given address has ownership of the given name. -func IsAuthorizedAddressForName(address std.Address, name string) bool { - return checkFunc(enabled, address, name) -} - -// VerifyNameByUser checks from the `users` package that the user has correctly -// registered the given name. -// This function considers as valid an `address` that matches the `name`. -func VerifyNameByUser(enable bool, address std.Address, name string) bool { - if !enable { - return true - } - - // Allow user with their own address as name - if address.String() == name { - return true - } - - if user := users.GetUserByName(name); user != nil { - return user.Address == address - } - - return false -} - -// Admin calls - -// Enable this package. -func AdminEnable() { - if !owner.CallerIsOwner() { - panic(ownable.ErrUnauthorized) - } - - enabled = true -} - -// Disable this package. -func AdminDisable() { - if !owner.CallerIsOwner() { - panic(ownable.ErrUnauthorized) - } - - enabled = false -} - -// AdminUpdateVerifyCall updates the method that verifies the namespace. -func AdminUpdateVerifyCall(check VerifyNameFunc) { - if !owner.CallerIsOwner() { - panic(ownable.ErrUnauthorized) - } - - checkFunc = check -} - -// AdminTransferOwnership transfers the ownership to a new owner. -func AdminTransferOwnership(newOwner std.Address) error { - if !owner.CallerIsOwner() { - panic(ownable.ErrUnauthorized) - } - - return owner.TransferOwnership(newOwner) -} diff --git a/examples/gno.land/r/sys/validators/v2/gnosdk.gno b/examples/gno.land/r/sys/validators/v2/gnosdk.gno index 0a85c0682b8..60b8f84966c 100644 --- a/examples/gno.land/r/sys/validators/v2/gnosdk.gno +++ b/examples/gno.land/r/sys/validators/v2/gnosdk.gno @@ -10,7 +10,7 @@ func GetChanges(from int64) []validators.Validator { valsetChanges := make([]validators.Validator, 0) // Gather the changes from the specified block - changes.Iterate(getBlockID(from), "", func(_ string, value interface{}) bool { + changes.Iterate(getBlockID(from), "", func(_ string, value any) bool { chs := value.([]change) for _, ch := range chs { diff --git a/examples/gno.land/r/sys/validators/v2/validators.gno b/examples/gno.land/r/sys/validators/v2/validators.gno index bf42ece4990..e6303510679 100644 --- a/examples/gno.land/r/sys/validators/v2/validators.gno +++ b/examples/gno.land/r/sys/validators/v2/validators.gno @@ -30,7 +30,7 @@ func addValidator(validator validators.Validator) { // Validator added, note the change ch := change{ - blockNum: std.GetHeight(), + blockNum: std.ChainHeight(), validator: val, } @@ -50,7 +50,7 @@ func removeValidator(address std.Address) { // Validator removed, note the change ch := change{ - blockNum: std.GetHeight(), + blockNum: std.ChainHeight(), validator: validators.Validator{ Address: val.Address, PubKey: val.PubKey, @@ -98,7 +98,7 @@ func Render(_ string) string { } output := "Valset changes:\n" - changes.ReverseIterateByOffset(size-maxDisplay, maxDisplay, func(_ string, value interface{}) bool { + changes.ReverseIterateByOffset(size-maxDisplay, maxDisplay, func(_ string, value any) bool { chs := value.([]change) for _, ch := range chs { diff --git a/examples/gno.land/r/sys/validators/v2/validators_test.gno b/examples/gno.land/r/sys/validators/v2/validators_test.gno index 177d84144cb..dc159a3b957 100644 --- a/examples/gno.land/r/sys/validators/v2/validators_test.gno +++ b/examples/gno.land/r/sys/validators/v2/validators_test.gno @@ -67,7 +67,7 @@ func TestValidators_AddRemove(t *testing.T) { } // Save the beginning height for the removal - initialRemoveHeight := std.GetHeight() + initialRemoveHeight := std.ChainHeight() // Clear any changes changes = avl.NewTree() diff --git a/examples/gno.land/r/ursulovic/home/home.gno b/examples/gno.land/r/ursulovic/home/home.gno index c03d8a66868..be96ab3f913 100644 --- a/examples/gno.land/r/ursulovic/home/home.gno +++ b/examples/gno.land/r/ursulovic/home/home.gno @@ -19,7 +19,6 @@ var ( githubUrl string linkedinUrl string - connectUrl string imageUpdatePrice int64 isValidUrl func(string) bool @@ -75,7 +74,7 @@ func UpdateSelectedImage(url string) { panic("Url is not valid!") } - sentCoins := std.GetOrigSend() + sentCoins := std.OriginSend() if len(sentCoins) != 1 && sentCoins.AmountOf("ugnot") == imageUpdatePrice { panic("Please send exactly " + strconv.Itoa(int(imageUpdatePrice)) + " ugnot") diff --git a/examples/gno.land/r/ursulovic/home/home_test.gno b/examples/gno.land/r/ursulovic/home/home_test.gno index ff3f763d62a..eea71c030e6 100644 --- a/examples/gno.land/r/ursulovic/home/home_test.gno +++ b/examples/gno.land/r/ursulovic/home/home_test.gno @@ -9,7 +9,7 @@ import ( func TestUpdateGithubUrl(t *testing.T) { caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) newUrl := "https://github.com/example" @@ -22,7 +22,7 @@ func TestUpdateGithubUrl(t *testing.T) { func TestUpdateLinkedinUrl(t *testing.T) { caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) newUrl := "https://www.linkedin.com/in/example" @@ -35,7 +35,7 @@ func TestUpdateLinkedinUrl(t *testing.T) { func TestUpdateAboutMe(t *testing.T) { caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) newAboutMe := "This is new description!" @@ -48,12 +48,12 @@ func TestUpdateAboutMe(t *testing.T) { func TestUpdateSelectedImage(t *testing.T) { var user = testutils.TestAddress("user") - std.TestSetOrigCaller(user) + std.TestSetOriginCaller(user) validImageUrl := "https://i.ibb.co/hLtmnX0/beautiful-rain-forest-ang-ka-nature-trail-doi-inthanon-national-park-thailand-36703721.webp" coinsSent := std.NewCoins(std.NewCoin("ugnot", 5000000)) // Update to match the price expected by your function - std.TestSetOrigSend(coinsSent, std.NewCoins()) + std.TestSetOriginSend(coinsSent, std.NewCoins()) UpdateSelectedImage(validImageUrl) @@ -72,7 +72,7 @@ func TestUpdateSelectedImage(t *testing.T) { UpdateSelectedImage(invalidImageUrl) invalidCoins := std.NewCoins(std.NewCoin("ugnot", 1000000)) - std.TestSetOrigSend(invalidCoins, std.NewCoins()) + std.TestSetOriginSend(invalidCoins, std.NewCoins()) defer func() { if r := recover(); r == nil { @@ -85,7 +85,7 @@ func TestUpdateSelectedImage(t *testing.T) { func TestUpdateImagePrice(t *testing.T) { caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") - std.TestSetOrigCaller(caller) + std.TestSetOriginCaller(caller) var newImageUpdatePrice int64 = 3000000 diff --git a/examples/gno.land/r/ursulovic/registry/registry.gno b/examples/gno.land/r/ursulovic/registry/registry.gno index 0bbd6c80df5..f873bc6a120 100644 --- a/examples/gno.land/r/ursulovic/registry/registry.gno +++ b/examples/gno.land/r/ursulovic/registry/registry.gno @@ -50,7 +50,7 @@ func SetBackupAddress(addr std.Address) error { // It will stay here for now, might be useful later func assertAuthorized() { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() isAuthorized := caller == mainAddress || caller == backupAddress if !isAuthorized { diff --git a/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno b/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno index c73e99cc583..92b277d1b9c 100644 --- a/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno +++ b/examples/gno.land/r/x/jeronimo_render_proxy/home/home.gno @@ -25,7 +25,7 @@ func Register(fn RenderFn) { } proxyPath := std.CurrentRealm().PkgPath() - callerPath := std.PrevRealm().PkgPath() + callerPath := std.PreviousRealm().PkgPath() if !strings.HasPrefix(callerPath, proxyPath+"/") { panic("caller realm path must start with " + proxyPath) } diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno index 1298b2539be..170aba1c71a 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v1/v1.gno @@ -24,9 +24,9 @@ func SetNextVersion(addr string) { // assert CallTx call. std.AssertOriginCall() // assert admin. - caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { - panic("should not happen") // because std.AssertOrigCall(). + caller := std.CallerAt(2) + if caller != std.OriginCaller() { + panic("should not happen") // because std.AssertOriginCall(). } if caller != admin { panic("unauthorized") diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno index bf30ee1acab..80723ad01f1 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_b/v2/v2.gno @@ -28,9 +28,9 @@ func SetNextVersion(addr string) { // assert CallTx call. std.AssertOriginCall() // assert admin. - caller := std.GetCallerAt(2) - if caller != std.GetOrigCaller() { - panic("should not happen") // because std.AssertOrigCall(). + caller := std.CallerAt(2) + if caller != std.OriginCaller() { + panic("should not happen") // because std.AssertOriginCall(). } if caller != admin { panic("unauthorized") diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno index 0a610b0b196..d71f5ec3db5 100644 --- a/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno @@ -23,7 +23,7 @@ func SetCurrentImpl(pkgpath string) { } func assertIsCurrentImpl() { - if std.PrevRealm().PkgPath() != currentImpl { + if std.PreviousRealm().PkgPath() != currentImpl { panic("unauthorized") } } diff --git a/examples/gno.land/r/x/map_delete/gno.mod b/examples/gno.land/r/x/map_delete/gno.mod new file mode 100644 index 00000000000..ec53f21a06f --- /dev/null +++ b/examples/gno.land/r/x/map_delete/gno.mod @@ -0,0 +1 @@ +module gno.land/r/x/map_delete diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/category.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/category.gno index 3ed46b38245..122f23df714 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/category.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/category.gno @@ -38,7 +38,7 @@ func (c Category) Status() string { func (c *Category) Tally() { // TODO error handling - c.votes.Iterate("", "", func(address string, vote interface{}) bool { + c.votes.Iterate("", "", func(address string, vote any) bool { v := vote.(Vote) value, exists := c.tallyResult.results.Get(v.option) if !exists { diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno index 1ec801bb971..67311d401b3 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee.gno @@ -36,7 +36,7 @@ func (c *Committee) DismissMembers(members []std.Address) []std.Address { func (c *Committee) AddCategory(name string, criteria []string) bool { // TODO error handling - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OriginCaller()) { return false } category := NewCategory(name, criteria) @@ -45,7 +45,7 @@ func (c *Committee) AddCategory(name string, criteria []string) bool { } func (c *Committee) ApproveCategory(name string, option string) bool { - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OriginCaller()) { return false } @@ -58,8 +58,8 @@ func (c *Committee) ApproveCategory(name string, option string) bool { return false } - vote := NewVote(std.GetOrigCaller(), option) - category.votes.Set(std.GetOrigCaller().String(), vote) + vote := NewVote(std.OriginCaller(), option) + category.votes.Set(std.OriginCaller().String(), vote) category.Tally() // TODO Add threshold factor for a category approval @@ -81,7 +81,7 @@ func (c *Committee) ApproveCategory(name string, option string) bool { // TODO error handling func (c *Committee) AddContribution(pr *PullRequest, contributor std.Address) (contributionId int, ok bool) { - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OriginCaller()) { return -1, false } // Check the category of the PR matches a category this committee evaluates @@ -95,7 +95,7 @@ func (c *Committee) AddContribution(pr *PullRequest, contributor std.Address) (c // TODO error handling func (c *Committee) ApproveContribution(id int, option string) bool { - if !c.isMember(std.GetOrigCaller()) { + if !c.isMember(std.OriginCaller()) { return false } @@ -109,7 +109,7 @@ func (c *Committee) ApproveContribution(id int, option string) bool { return false } - vote := NewVote(std.GetOrigCaller(), option) + vote := NewVote(std.OriginCaller(), option) contribution.votes = append(contribution.votes, vote) contribution.Tally() diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno index 8a3d16fd7f7..39e7fb6cabf 100644 --- a/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/committee_test.gno @@ -36,7 +36,7 @@ func TestCategoryEvaluationCriteria(t *testing.T) { c.DesignateMembers([]std.Address{member}) t.Run("Add First Committee Category and Evaluation Criteria", func(t *testing.T) { - std.TestSetOrigCaller(member) + std.TestSetOriginCaller(member) c.AddCategory(category, criteria) value, exists := c.categories.Get(category) if !exists { @@ -49,7 +49,7 @@ func TestCategoryEvaluationCriteria(t *testing.T) { }) t.Run("Add Second Committee Category and Evaluation Criteria", func(t *testing.T) { - std.TestSetOrigCaller(member) + std.TestSetOriginCaller(member) c.AddCategory(category2, criteria2) value2, exists2 := c.categories.Get(category2) if !exists2 { @@ -62,7 +62,7 @@ func TestCategoryEvaluationCriteria(t *testing.T) { }) t.Run("Approve First Committee Category", func(t *testing.T) { - std.TestSetOrigCaller(member) + std.TestSetOriginCaller(member) approved := c.ApproveCategory(category, VoteYes) if !approved { value, exists := c.categories.Get(category) diff --git a/examples/gno.land/r/x/nir1218_evaluation_proposal/gno.mod b/examples/gno.land/r/x/nir1218_evaluation_proposal/gno.mod new file mode 100644 index 00000000000..6dc86fb4f01 --- /dev/null +++ b/examples/gno.land/r/x/nir1218_evaluation_proposal/gno.mod @@ -0,0 +1 @@ +module gno.land/r/x/nir1218_evaluation_proposal diff --git a/examples/gno.land/r/x/skip_height_to_skip_time/gno.mod b/examples/gno.land/r/x/skip_height_to_skip_time/gno.mod new file mode 100644 index 00000000000..67b82791235 --- /dev/null +++ b/examples/gno.land/r/x/skip_height_to_skip_time/gno.mod @@ -0,0 +1 @@ +module gno.land/r/x/skip_height_to_skip_time diff --git a/examples/gno.land/r/x/skip_height_to_skip_time/skiptime_test.gno b/examples/gno.land/r/x/skip_height_to_skip_time/skiptime_test.gno index 52670a5626b..eff6e669be0 100644 --- a/examples/gno.land/r/x/skip_height_to_skip_time/skiptime_test.gno +++ b/examples/gno.land/r/x/skip_height_to_skip_time/skiptime_test.gno @@ -7,7 +7,7 @@ import ( ) func TestSkipHeights(t *testing.T) { - oldHeight := std.GetHeight() + oldHeight := std.ChainHeight() shouldEQ(t, oldHeight, 123) oldNow := time.Now().Unix() @@ -16,7 +16,7 @@ func TestSkipHeights(t *testing.T) { // skip 3 blocks == 15 seconds std.TestSkipHeights(3) - shouldEQ(t, std.GetHeight()-oldHeight, 3) + shouldEQ(t, std.ChainHeight()-oldHeight, 3) shouldEQ(t, time.Now().Unix()-oldNow, 15) } diff --git a/examples/no_cycles_test.go b/examples/no_cycles_test.go new file mode 100644 index 00000000000..4c89c112178 --- /dev/null +++ b/examples/no_cycles_test.go @@ -0,0 +1,214 @@ +package examples_test + +import ( + "fmt" + "io/fs" + "os" + pathlib "path" + "path/filepath" + "slices" + "strings" + "testing" + + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/packages" + "github.com/stretchr/testify/require" +) + +// XXX: move this into `gno lint` + +var injectedTestingLibs = []string{"encoding/json", "fmt", "os", "os_test"} + +// TestNoCycles checks that there is no import cycles in stdlibs and non-draft examples +func TestNoCycles(t *testing.T) { + // find stdlibs + gnoRoot := gnoenv.RootDir() + pkgs, err := listPkgs(gnomod.Pkg{ + Dir: filepath.Join(gnoRoot, "gnovm", "stdlibs"), + Name: "", + }) + require.NoError(t, err) + + // find examples + examples, err := gnomod.ListPkgs(filepath.Join(gnoRoot, "examples")) + require.NoError(t, err) + for _, example := range examples { + if example.Draft { + continue + } + examplePkgs, err := listPkgs(example) + require.NoError(t, err) + pkgs = append(pkgs, examplePkgs...) + } + + // detect cycles + visited := make(map[string]bool) + for _, p := range pkgs { + require.NoError(t, detectCycles(p, pkgs, visited)) + } +} + +// detectCycles detects import cycles +// +// We need to check +// 3 kinds of nodes +// +// - normal pkg: compiled source +// +// - xtest pkg: external test source (include xtests and filetests), can be treated as their own package +// +// - test pkg: embedded test sources, +// these should not have their corresponding normal package in their dependencies tree +// +// The tricky thing is that we need to split test sources and normal source +// while not considering them as distincitive packages. +// Otherwise we will have false positive for example if we have these edges: +// +// - foo_pkg/foo_test.go imports bar_pkg +// +// - bar_pkg/bar_test.go import foo_pkg +// +// In go, the above example is allowed +// but the following is not +// +// - foo_pkg/foo.go imports bar_pkg +// +// - bar_pkg/bar_test.go imports foo_pkg +func detectCycles(root testPkg, pkgs []testPkg, visited map[string]bool) error { + // check cycles in package's sources + stack := []string{} + if err := visitPackage(root, pkgs, visited, stack); err != nil { + return fmt.Errorf("pkgsrc import: %w", err) + } + // check cycles in external tests' dependencies we might have missed + if err := visitImports([]packages.FileKind{packages.FileKindXTest, packages.FileKindFiletest}, root, pkgs, visited, stack); err != nil { + return fmt.Errorf("xtest import: %w", err) + } + + // check cycles in tests' imports by marking the current package as visited while visiting the tests' imports + // we also consider PackageSource imports here because tests can call package code + visited = map[string]bool{root.PkgPath: true} + stack = []string{root.PkgPath} + if err := visitImports([]packages.FileKind{packages.FileKindPackageSource, packages.FileKindTest}, root, pkgs, visited, stack); err != nil { + return fmt.Errorf("test import: %w", err) + } + + return nil +} + +// visitImports resolves and visits imports by kinds +func visitImports(kinds []packages.FileKind, root testPkg, pkgs []testPkg, visited map[string]bool, stack []string) error { + for _, imp := range root.Imports.Merge(kinds...) { + if slices.Contains(injectedTestingLibs, imp.PkgPath) { + continue + } + idx := slices.IndexFunc(pkgs, func(p testPkg) bool { return p.PkgPath == imp.PkgPath }) + if idx == -1 { + return fmt.Errorf("import %q not found for %q tests", imp.PkgPath, root.PkgPath) + } + if err := visitPackage(pkgs[idx], pkgs, visited, stack); err != nil { + return fmt.Errorf("test import error: %w", err) + } + } + + return nil +} + +// visitNode visits a package and its imports recursively. It only considers imports in PackageSource +func visitPackage(pkg testPkg, pkgs []testPkg, visited map[string]bool, stack []string) error { + if slices.Contains(stack, pkg.PkgPath) { + return fmt.Errorf("cycle detected: %s -> %s", strings.Join(stack, " -> "), pkg.PkgPath) + } + if visited[pkg.PkgPath] { + return nil + } + + visited[pkg.PkgPath] = true + stack = append(stack, pkg.PkgPath) + + if err := visitImports([]packages.FileKind{packages.FileKindPackageSource}, pkg, pkgs, visited, stack); err != nil { + return err + } + + return nil +} + +type testPkg struct { + Dir string + PkgPath string + Imports packages.ImportsMap +} + +// listPkgs lists all packages in rootMod +func listPkgs(rootMod gnomod.Pkg) ([]testPkg, error) { + res := []testPkg{} + rootDir := rootMod.Dir + visited := map[string]struct{}{} + if err := fs.WalkDir(os.DirFS(rootDir), ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if !strings.HasSuffix(d.Name(), ".gno") { + return nil + } + subPath := filepath.Dir(path) + dir := filepath.Join(rootDir, subPath) + if _, ok := visited[dir]; ok { + return nil + } + visited[dir] = struct{}{} + + subPkgPath := pathlib.Join(rootMod.Name, subPath) + + pkg := testPkg{ + Dir: dir, + PkgPath: subPkgPath, + } + + memPkg, err := readPkg(pkg.Dir, pkg.PkgPath) + if err != nil { + return fmt.Errorf("read pkg %q: %w", pkg.Dir, err) + } + pkg.Imports, err = packages.Imports(memPkg, nil) + if err != nil { + return fmt.Errorf("list imports of %q: %w", memPkg.Path, err) + } + + res = append(res, pkg) + return nil + }); err != nil { + return nil, fmt.Errorf("walk dirs at %q: %w", rootDir, err) + } + return res, nil +} + +// readPkg reads the sources of a package. It includes all .gno files but ignores the package name +func readPkg(dir string, pkgPath string) (*gnovm.MemPackage, error) { + list, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + memPkg := &gnovm.MemPackage{Path: pkgPath} + for _, entry := range list { + fpath := filepath.Join(dir, entry.Name()) + if !strings.HasSuffix(fpath, ".gno") { + continue + } + fname := filepath.Base(fpath) + bz, err := os.ReadFile(fpath) + if err != nil { + return nil, err + } + memPkg.Files = append(memPkg.Files, + &gnovm.MemFile{ + Name: fname, + Body: string(bz), + }) + } + return memPkg, nil +} diff --git a/gno.land/Makefile b/gno.land/Makefile index 075560f44a9..90ba7451c35 100644 --- a/gno.land/Makefile +++ b/gno.land/Makefile @@ -50,9 +50,13 @@ install.gnokey:; go install ./cmd/gnokey .PHONY: dev.gnoweb generate.gnoweb dev.gnoweb: make -C ./pkg/gnoweb dev -generate.gnoweb: + +.PHONY: generate +generate: + go generate -x ./... make -C ./pkg/gnoweb generate + .PHONY: fclean fclean: clean rm -rf gnoland-data genesis.json diff --git a/gno.land/cmd/gnoland/imports_test.go b/gno.land/cmd/gnoland/imports_test.go new file mode 100644 index 00000000000..c5ae81599b4 --- /dev/null +++ b/gno.land/cmd/gnoland/imports_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNoTestingStdlibImport(t *testing.T) { + // See: https://github.com/gnolang/gno/issues/3585 + // The gno.land binary should not import testing stdlibs, which contain unsafe + // code in the respective native bindings. + + res, err := exec.Command("go", "list", "-f", `{{ join .Deps "\n" }}`, ".").CombinedOutput() + require.NoError(t, err) + assert.Contains(t, string(res), "github.com/gnolang/gno/gnovm/stdlibs\n", "should contain normal stdlibs") + assert.NotContains(t, string(res), "github.com/gnolang/gno/gnovm/tests/stdlibs\n", "should not contain test stdlibs") +} diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index 6500e44fcc4..8586e88e1af 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -23,9 +23,11 @@ type webCfg struct { bind string faucetURL string assetsDir string + timeout time.Duration analytics bool json bool html bool + noStrict bool verbose bool } @@ -33,6 +35,7 @@ var defaultWebOptions = webCfg{ chainid: "dev", remote: "127.0.0.1:26657", bind: ":8888", + timeout: time.Minute, } func main() { @@ -127,7 +130,14 @@ func (c *webCfg) RegisterFlags(fs *flag.FlagSet) { &c.analytics, "with-analytics", defaultWebOptions.analytics, - "nable privacy-first analytics", + "enable privacy-first analytics", + ) + + fs.BoolVar( + &c.noStrict, + "no-strict", + defaultWebOptions.noStrict, + "allow cross-site resource forgery and disable https enforcement", ) fs.BoolVar( @@ -136,6 +146,13 @@ func (c *webCfg) RegisterFlags(fs *flag.FlagSet) { defaultWebOptions.verbose, "verbose logging mode", ) + + fs.DurationVar( + &c.timeout, + "timeout", + defaultWebOptions.timeout, + "set read/write/idle timeout for server connections", + ) } func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { @@ -144,7 +161,6 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { if cfg.verbose { level = zapcore.DebugLevel } - var zapLogger *zap.Logger if cfg.json { zapLogger = log.NewZapJSONLogger(io.Out(), level) @@ -155,23 +171,24 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger := log.ZapLoggerToSlog(zapLogger) + // Setup app appcfg := gnoweb.NewDefaultAppConfig() appcfg.ChainID = cfg.chainid appcfg.NodeRemote = cfg.remote appcfg.RemoteHelp = cfg.remoteHelp + if appcfg.RemoteHelp == "" { + appcfg.RemoteHelp = appcfg.NodeRemote + } appcfg.Analytics = cfg.analytics appcfg.UnsafeHTML = cfg.html appcfg.FaucetURL = cfg.faucetURL appcfg.AssetsDir = cfg.assetsDir - if appcfg.RemoteHelp == "" { - appcfg.RemoteHelp = appcfg.NodeRemote - } - app, err := gnoweb.NewRouter(logger, appcfg) if err != nil { return nil, fmt.Errorf("unable to start gnoweb app: %w", err) } + // Resolve binding address bindaddr, err := net.ResolveTCPAddr("tcp", cfg.bind) if err != nil { return nil, fmt.Errorf("unable to resolve listener %q: %w", cfg.bind, err) @@ -179,18 +196,60 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger.Info("Running", "listener", bindaddr.String()) + // Setup security headers + secureHandler := SecureHeadersMiddleware(app, !cfg.noStrict) + + // Setup server server := &http.Server{ - Handler: app, + Handler: secureHandler, Addr: bindaddr.String(), - ReadHeaderTimeout: 60 * time.Second, + ReadTimeout: cfg.timeout, // Time to read the request + WriteTimeout: cfg.timeout, // Time to write the entire response + IdleTimeout: cfg.timeout, // Time to keep idle connections open + ReadHeaderTimeout: time.Minute, // Time to read request headers } return func() error { if err := server.ListenAndServe(); err != nil { - logger.Error("HTTP server stopped", " error:", err) + logger.Error("HTTP server stopped", "error", err) return commands.ExitCodeError(1) } return nil }, nil } + +func SecureHeadersMiddleware(next http.Handler, strict bool) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Prevent MIME type sniffing by browsers. This ensures that the browser + // does not interpret files as a different MIME type than declared. + w.Header().Set("X-Content-Type-Options", "nosniff") + + // Prevent the page from being embedded in an iframe. This mitigates + // clickjacking attacks by ensuring the page cannot be loaded in a frame. + w.Header().Set("X-Frame-Options", "DENY") + + // Control the amount of referrer information sent in the Referer header. + // 'no-referrer' ensures that no referrer information is sent, which + // enhances privacy and prevents leakage of sensitive URLs. + w.Header().Set("Referrer-Policy", "no-referrer") + + // In `strict` mode, prevent cross-site ressources forgery and enforce https + if strict { + // Define a Content Security Policy (CSP) to restrict the sources of + // scripts, styles, images, and other resources. This helps prevent + // cross-site scripting (XSS) and other code injection attacks. + // - 'self' allows resources from the same origin. + // - 'data:' allows inline images (e.g., base64-encoded images). + // - 'https://gnolang.github.io' allows images from this specific domain - used by gno.land. TODO: use a proper generic whitelisted service + w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' https://sa.gno.services; style-src 'self'; img-src 'self' data: https://gnolang.github.io https://assets.gnoteam.com https://sa.gno.services; font-src 'self'") + + // Enforce HTTPS by telling browsers to only access the site over HTTPS + // for a specified duration (1 year in this case). This also applies to + // subdomains and allows preloading into the browser's HSTS list. + w.Header().Set("Strict-Transport-Security", "max-age=31536000") + } + + next.ServeHTTP(w, r) + }) +} diff --git a/gno.land/cmd/gnoweb/main_test.go b/gno.land/cmd/gnoweb/main_test.go index 37006c18c93..9690db47cd5 100644 --- a/gno.land/cmd/gnoweb/main_test.go +++ b/gno.land/cmd/gnoweb/main_test.go @@ -1,7 +1,10 @@ package main import ( + "net/http" + "net/http/httptest" "os" + "strings" "testing" "github.com/gnolang/gno/tm2/pkg/commands" @@ -23,3 +26,80 @@ func TestSetupWeb(t *testing.T) { _, err = setupWeb(&opts, []string{}, stdio) require.NoError(t, err) } + +// Dummy handler to simulate the processing chain. +// It now returns a more detailed message in the response body. +func dummyHandler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("OK")) +} + +func TestSecureHeadersMiddlewareStrict(t *testing.T) { + handler := SecureHeadersMiddleware(http.HandlerFunc(dummyHandler), true) + + req := httptest.NewRequest("GET", "http://example.com", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + res := rec.Result() + + // Check common headers. + if res.Header.Get("X-Content-Type-Options") != "nosniff" { + t.Errorf("Expected X-Content-Type-Options 'nosniff', got '%s'", res.Header.Get("X-Content-Type-Options")) + } + if res.Header.Get("X-Frame-Options") != "DENY" { + t.Errorf("Expected X-Frame-Options 'DENY', got '%s'", res.Header.Get("X-Frame-Options")) + } + if res.Header.Get("Referrer-Policy") != "no-referrer" { + t.Errorf("Expected Referrer-Policy 'no-referrer', got '%s'", res.Header.Get("Referrer-Policy")) + } + + // Check headers specific to strict mode. + csp := res.Header.Get("Content-Security-Policy") + if !strings.Contains(csp, "https://assets.gnoteam.com") { + t.Errorf("Expected Content-Security-Policy to contain 'https://assets.gnoteam.com', got '%s'", csp) + } + if res.Header.Get("Strict-Transport-Security") != "max-age=31536000" { + t.Errorf("Expected Strict-Transport-Security 'max-age=31536000', got '%s'", res.Header.Get("Strict-Transport-Security")) + } + + // Optionally, verify the response body. + body := rec.Body.String() + if !strings.Contains(body, "OK") { + t.Errorf("Unexpected response body: %s", body) + } +} + +func TestSecureHeadersMiddlewareNonStrict(t *testing.T) { + handler := SecureHeadersMiddleware(http.HandlerFunc(dummyHandler), false) + + req := httptest.NewRequest("GET", "http://example.com", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + res := rec.Result() + + // Check that the common headers are set. + if res.Header.Get("X-Content-Type-Options") != "nosniff" { + t.Errorf("Expected X-Content-Type-Options 'nosniff', got '%s'", res.Header.Get("X-Content-Type-Options")) + } + if res.Header.Get("X-Frame-Options") != "DENY" { + t.Errorf("Expected X-Frame-Options 'DENY', got '%s'", res.Header.Get("X-Frame-Options")) + } + if res.Header.Get("Referrer-Policy") != "no-referrer" { + t.Errorf("Expected Referrer-Policy 'no-referrer', got '%s'", res.Header.Get("Referrer-Policy")) + } + + // In non-strict mode, CSP and HSTS should not be defined. + if csp := res.Header.Get("Content-Security-Policy"); csp != "" { + t.Errorf("Did not expect Content-Security-Policy in non-strict mode, got '%s'", csp) + } + if hsts := res.Header.Get("Strict-Transport-Security"); hsts != "" { + t.Errorf("Did not expect Strict-Transport-Security in non-strict mode, got '%s'", hsts) + } + + // Optionally, verify the response body. + body := rec.Body.String() + if !strings.Contains(body, "OK") { + t.Errorf("Unexpected response body: %s", body) + } +} diff --git a/gno.land/genesis/genesis_params.toml b/gno.land/genesis/genesis_params.toml index fb080024624..52ed8526958 100644 --- a/gno.land/genesis/genesis_params.toml +++ b/gno.land/genesis/genesis_params.toml @@ -1,29 +1,29 @@ -## gno.land -["gno.land/r/sys/params.sys"] - users_pkgpath.string = "gno.land/r/sys/users" # if empty, no namespace support. - # TODO: validators_pkgpath.string = "gno.land/r/sys/validators" - # TODO: rewards_pkgpath.string = "gno.land/r/sys/rewards" - # TODO: token_lock.bool = true +## gno.land params -## gnovm -["gno.land/r/sys/params.vm"] - chain_domain.string = "gno.land" - # TODO: max_gas.int64 = 100_000_000 - # TODO: chain_tz.string = "UTC" - # TODO: default_storage_allowance.string = "" +["bank"] + restricted_denoms = ["gnot"] -## tm2 -["gno.land/r/sys/params.tm2"] +["vm"] + chain_domain = "gno.land" + sysnames_pkgpath = "gno.land/r/sys/names" + # TODO: Leverage toml unmarshaler to extract these into VM Params struct before writing to genesis + # TODO: max_gas = 100_000_000 + # TODO: chain_tz = "UTC" + # TODO: default_storage_allowance = "" -## misc -["gno.land/r/sys/params.misc"] +["tm2"] + # TODO -## testing -# do not remove these lines. they are needed for a txtar integration test. -["gno.land/r/sys/params.test"] - foo.string = "bar" - foo.int64 = -1337 - foo.uint64 = 42 - foo.bool = true - #foo.bytes = todo +####################################### +## testing realm parameters +## Do not remove these lines. +## They are needed for a txtar integration test. +## XXX copy these (and above) to say genesis_params_test.toml. +["vm:gno.land/r/sys/testrealm"] + foo_string = "bar" + foo_int64 = -1337 + foo_uint64 = 123 + foo_bool = true + "foo_strings.strings" = ["some", "strings"] + # foo_bytes = ...? diff --git a/gno.land/genesis/genesis_txs.jsonl b/gno.land/genesis/genesis_txs.jsonl index 9027d51c0ac..b42d6cc52c4 100644 --- a/gno.land/genesis/genesis_txs.jsonl +++ b/gno.land/genesis/genesis_txs.jsonl @@ -1,17 +1,14 @@ -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj:10\ng1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s:1\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8:1\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q:1\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj:1\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0:1\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz:1\ng187982000zsc493znqt828s90cmp6hcp2erhu6m:1\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl:1\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037:1\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5:1\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr:1\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz:1\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w:1\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz:1\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3:1\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0:1\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n:1\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac:1\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap:1\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv:1\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv:1\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq:1\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6:1\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q:1\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7:1\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k:1\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll:1\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd:1\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64:1\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw:1\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a:1\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc:1\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6:1\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6:1\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9:1\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea:1\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3:1\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp:1\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5:1\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf:1\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g:1\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r:1\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su:1\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69:1\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6:1\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa:10\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t:5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"S8iMMzlOMK8dmox78R9Z8+pSsS8YaTCXrIcaHDpiOgkOy7gqoQJ0oftM0zf8zAz4xpezK8Lzg8Q0fCdXJxV76w=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1thlf3yct7n7ex70k0p62user0kn6mj6d3s0cg3\ng1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\ng1manfred47kzduec920z88wfr64ylksmdcedlf5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"njczE6xYdp01+CaUU/8/v0YC/NuZD06+qLind+ZZEEMNaRe/4Ln+4z7dG6HYlaWUMsyI1KCoB6NIehoE0PZ44Q=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz\ng187982000zsc493znqt828s90cmp6hcp2erhu6m\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6\ng1manfred47kzduec920z88wfr64ylksmdcedlf5\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t\n"]}],"fee":{"gas_wanted":"4000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"7AmlhZhsVkxCUl0bbpvpPMnIKihwtG7A5IFR6Tg4xStWLgaUr05XmWRKlO2xjstTtwbVKQT5mFL4h5wyX4SQzw=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","administrator","g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"AqCqe0cS55Ym7/BvPDoCDyPP5q8284gecVQ2PMOlq/4lJpO9Q18SOWKI15dMEBY1pT0AYyhCeTirlsM1I3Y4Cg=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","zo_oma","Love is the encryption key\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A6yg5/iiktruezVw5vZJwLlGwyrvw8RlqOToTRMWXkE2"},"signature":"GGp+bVL2eEvKecPqgcULSABYOSnSMnJzfIsR8ZIRER1GGX/fOiCReX4WKMrGLVROJVfbLQkDRwvhS4TLHlSoSQ=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["g1manfred47kzduec920z88wfr64ylksmdcedlf5","moul","https://github.com/moul"]}],"fee":{"gas_wanted":"2000000","gas_fee":"200000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"9CWeNbKx+hEL+RdHplAVAFntcrAVx5mK9tMqoywuHVoreH844n3yOxddQrGfBk6T2tMBmNWakERRqWZfS+bYAQ=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","piupiu","@piux2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Ar68lqbU2YC63fbMcYUtJhYO3/66APM/EqF7m0nUjGyz"},"signature":"pTUpP0d/XlfVe3TH1hlaoLhKadzIKG1gtQ/Ueuat72p+659RWRea58Z0mk6GgPE/EeTbhMEY45zufevBdGJVoQ=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5","send":"","pkg_path":"gno.land/r/demo/users","func":"Register","args":["g1manfred47kzduec920z88wfr64ylksmdcedlf5","anarcher","https://twitter.com/anarcher"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AjpLbKdQeH+yB/1OCB148l5GlRRrXma71hdA8EES3H7f"},"signature":"pf5xm8oWIQIOEwSGw4icPmynLXb1P1HxKfjeh8UStU1mlIBPKa7yppeIMPpAflC0o2zjFR7Axe7CimAebm3BHg=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g15gdm49ktawvkrl88jadqpucng37yxutucuwaef","send":"200000000ugnot","pkg_path":"gno.land/r/demo/users","func":"Register","args":["","ideamour","\u003c3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AhClx4AsDuX3DNCPxhDwWnrfd4MIZmxJE4vt47ClVvT2"},"signature":"IQe64af878k6HjLDqIJeg27GXAVF6xS+96cDe2jMlxNV6+8sOcuUctp0GiWVnYfN4tpthC6d4WhBo+VlpHqkbg=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateBoard","args":["testboard"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"vzlSxEFh5jOkaSdv3rsV91v/OJKEF2qSuoCpri1u5tRWq62T7xr3KHRCF5qFnn4aQX/yE8g8f/Y//WPOCUGhJw=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"testboard\\\")\"\n```\n\n### create a post of a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreatePost --args 1 --args \"Hello World\" --args#file \"./examples/gno.land/r/demo/boards/README.md\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createpost.unsigned.txt\n./build/gnokey sign test1 --txpath createpost.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 3 \u003e createpost.signed.txt\n./build/gnokey broadcast createpost.signed.txt --remote %%REMOTE%%\n```\n\n### create a comment to a post.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateReply --args 1 --args 1 --args \"A comment\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createcomment.unsigned.txt\n./build/gnokey sign test1 --txpath createcomment.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 4 \u003e createcomment.signed.txt\n./build/gnokey broadcast createcomment.signed.txt --remote %%REMOTE%%\n```\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard/1\"\n```\n\n### render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:testboard` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"V43B1waFxhzheW9TfmCpjLdrC4dC1yjUGES5y3J6QsNar6hRpNz4G1thzWmWK7xXhg8u1PCIpxLxGczKQYhuPw=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","NFT example","NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n - [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n - [gno.land/r/demo/nft/nft.gno](https://gno.land/r/demo/nft/nft.gno)\n - [zrealm_nft3.gno test](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/nft/z_3_filetest.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:testboard/1)).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"ZXfrTiHxPFQL8uSm+Tv7WXIHPMca9okhm94RAlC6YgNbB1VHQYYpoP4w+cnL3YskVzGrOZxensXa9CAZ+cNNeg=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.GetOrigSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":"iZX/llZlNTdZMLv1goCTgK2bWqzT8enlTq56wMTCpVxJGA0BTvuEM5Nnt9vrnlG6Taqj2GuTrmEnJBkDFTmt9g=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","TASK: Describe in your words","Describe in an essay (250+ words), on your favorite medium, why you are interested in gno.land and gnolang.\n\nReply here with a URL link to your written piece as a comment, for rewards.\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"4HBNtrta8HdeHj4JTN56PBTRK8GOe31NMRRXDiyYtjozuyRdWfOGEsGjGgHWcoBUJq6DepBgD4FetdqfhZ6TNQ=="}],"memo":""}} -{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:BOARDNAME/1\" --remote %%REMOTE%%\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:gnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"1000000ugnot","pkg_path":"gno.land/r/gnoland/users/v1","func":"Register","args":["administrator123"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt","send":"1000000ugnot","pkg_path":"gno.land/r/gnoland/users/v1","func":"Register","args":["zo_oma123"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A6yg5/iiktruezVw5vZJwLlGwyrvw8RlqOToTRMWXkE2"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"1000000ugnot","pkg_path":"gno.land/r/gnoland/users/v1","func":"Register","args":["moul001"]}],"fee":{"gas_wanted":"2000000","gas_fee":"200000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9","send":"1000000ugnot","pkg_path":"gno.land/r/gnoland/users/v1","func":"Register","args":["piupiu123"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Ar68lqbU2YC63fbMcYUtJhYO3/66APM/EqF7m0nUjGyz"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5","send":"1000000ugnot","pkg_path":"gno.land/r/gnoland/users/v1","func":"Register","args":["anarcher123"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AjpLbKdQeH+yB/1OCB148l5GlRRrXma71hdA8EES3H7f"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g15gdm49ktawvkrl88jadqpucng37yxutucuwaef","send":"1000000ugnot","pkg_path":"gno.land/r/gnoland/users/v1","func":"Register","args":["ideamour123"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AhClx4AsDuX3DNCPxhDwWnrfd4MIZmxJE4vt47ClVvT2"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateBoard","args":["testboard"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Hello World","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm \nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n## Starting the `gnoland` node node/validator.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### build gnoland.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake \n```\n\n### add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mnemonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### start gnoland validator node.\n\n```bash\n./build/gnoland\n```\n\n(This can be reset with `make reset`).\n\n### start gnoland web server (optional).\n\n```bash\ngo run ./gnoland/website\n```\n\n## Signing and broadcasting transactions.\n\n### publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 2000000 \u003e addpkg.avl.unsigned.txt\n./build/gnokey query \"auth/accounts/g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"\n./build/gnokey sign test1 --txpath addpkg.avl.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 0 \u003e addpkg.avl.signed.txt\n./build/gnokey broadcast addpkg.avl.signed.txt --remote %%REMOTE%%\n```\n\n### publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100ugnot --gas-fee 1ugnot --gas-wanted 300000000 \u003e addpkg.boards.unsigned.txt\n./build/gnokey sign test1 --txpath addpkg.boards.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 1 \u003e addpkg.boards.signed.txt\n./build/gnokey broadcast addpkg.boards.signed.txt --remote %%REMOTE%%\n```\n\n### create a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateBoard --args \"testboard\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createboard.unsigned.txt\n./build/gnokey sign test1 --txpath createboard.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 2 \u003e createboard.signed.txt\n./build/gnokey broadcast createboard.signed.txt --remote %%REMOTE%%\n```\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"testboard\\\")\"\n```\n\n### create a post of a board with a smart contract call.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreatePost --args 1 --args \"Hello World\" --args#file \"./examples/gno.land/r/demo/boards/README.md\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createpost.unsigned.txt\n./build/gnokey sign test1 --txpath createpost.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 3 \u003e createpost.signed.txt\n./build/gnokey broadcast createpost.signed.txt --remote %%REMOTE%%\n```\n\n### create a comment to a post.\n\n```bash\n./build/gnokey maketx call test1 --pkgpath \"gno.land/r/demo/boards\" --func CreateReply --args 1 --args 1 --args \"A comment\" --gas-fee 1ugnot --gas-wanted 2000000 \u003e createcomment.unsigned.txt\n./build/gnokey sign test1 --txpath createcomment.unsigned.txt --chainid \"%%CHAINID%%\" --number 0 --sequence 4 \u003e createcomment.signed.txt\n./build/gnokey broadcast createcomment.signed.txt --remote %%REMOTE%%\n```\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard/1\"\n```\n\n### render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:testboard` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:testboard\"\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","NFT example","NFT's are all the rage these days, for various reasons.\n\nI read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality.\n\n - [EIP-721](https://eips.ethereum.org/EIPS/eip-721)\n - [gno.land/r/demo/nft/nft.gno](https://gno.land/r/demo/nft/nft.gno)\n - [zrealm_nft3.gno test](https://github.com/gnolang/gno/blob/master/examples/gno.land/r/demo/nft/z_3_filetest.gno)\n\nIn short, this demonstrates how to implement Ethereum contract interfaces in gno.land; by using only standard Go language features.\n\nPlease leave a comment ([guide](https://gno.land/r/demo/boards:testboard/1)).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Simple echo example with coins","This is a simple test realm contract that demonstrates how to use the banker.\n\nSee [gno.land/r/demo/banktest/banktest.gno](/r/demo/banktest/banktest.gno) to see the original contract code.\n\nThis article will go through each line to explain how it works.\n\n```go\npackage banktest\n```\n\nThis package is locally named \"banktest\" (could be anything).\n\n```go\nimport (\n\t\"std\"\n)\n```\n\nThe \"std\" package is defined by the gno code in stdlibs/std/. \u003c/br\u003e\nSelf explanatory; and you'll see more usage from std later.\n\n```go\ntype activity struct {\n\tcaller std.Address\n\tsent std.Coins\n\treturned std.Coins\n\ttime std.Time\n}\n\nfunc (act *activity) String() string {\n\treturn act.caller.String() + \" \" +\n\t\tact.sent.String() + \" sent, \" +\n\t\tact.returned.String() + \" returned, at \" +\n\t\tstd.FormatTimestamp(act.time, \"2006-01-02 3:04pm MST\")\n}\n\nvar latest [10]*activity\n```\n\nThis is just maintaining a list of recent activity to this contract.\nNotice that the \"latest\" variable is defined \"globally\" within\nthe context of the realm with path \"gno.land/r/demo/banktest\".\n\nThis means that calls to functions defined within this package\nare encapsulated within this \"data realm\", where the data is \nmutated based on transactions that can potentially cross many\nrealm and non-realm packge boundaries (in the call stack).\n\n```go\n// Deposit will take the coins (to the realm's pkgaddr) or return them to user.\nfunc Deposit(returnDenom string, returnAmount int64) string {\n\tstd.AssertOriginCall()\n\tcaller := std.GetOrigCaller()\n\tsend := std.Coins{{returnDenom, returnAmount}}\n```\n\nThis is the beginning of the definition of the contract function named\n\"Deposit\". `std.AssertOriginCall() asserts that this function was called by a\ngno transactional Message. The caller is the user who signed off on this\ntransactional message. Send is the amount of deposit sent along with this\nmessage.\n\n```go\n\t// record activity\n\tact := \u0026activity{\n\t\tcaller: caller,\n\t\tsent: std.GetOrigSend(),\n\t\treturned: send,\n\t\ttime: std.GetTimestamp(),\n\t}\n\tfor i := len(latest) - 2; i \u003e= 0; i-- {\n\t\tlatest[i+1] = latest[i] // shift by +1.\n\t}\n\tlatest[0] = act\n```\n\nUpdating the \"latest\" array for viewing at gno.land/r/demo/banktest: (w/ trailing colon).\n\n```go\n\t// return if any.\n\tif returnAmount \u003e 0 {\n```\n\nIf the user requested the return of coins...\n\n```go\n\t\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n```\n\nuse a std.Banker instance to return any deposited coins to the original sender.\n\n```go\n\t\tpkgaddr := std.GetOrigPkgAddr()\n\t\t// TODO: use std.Coins constructors, this isn't generally safe.\n\t\tbanker.SendCoins(pkgaddr, caller, send)\n\t\treturn \"returned!\"\n```\n\nNotice that each realm package has an associated Cosmos address.\n\n\nFinally, the results are rendered via an ABCI query call when you visit [/r/demo/banktest:](/r/demo/banktest:).\n\n```go\nfunc Render(path string) string {\n\t// get realm coins.\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\tcoins := banker.GetCoins(std.GetOrigPkgAddr())\n\n\t// render\n\tres := \"\"\n\tres += \"## recent activity\\n\"\n\tres += \"\\n\"\n\tfor _, act := range latest {\n\t\tif act == nil {\n\t\t\tbreak\n\t\t}\n\t\tres += \" * \" + act.String() + \"\\n\"\n\t}\n\tres += \"\\n\"\n\tres += \"## total deposits\\n\"\n\tres += coins.String()\n\treturn res\n}\n```\n\nYou can call this contract yourself, by vistiing [/r/demo/banktest](/r/demo/banktest) and the [quickstart guide](/r/demo/boards:testboard/4).\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A+FhNtsXHjLfSJk1lB8FbiL4mGPjc50Kt81J7EKDnJ2y"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","TASK: Describe in your words","Describe in an essay (250+ words), on your favorite medium, why you are interested in gno.land and gnolang.\n\nReply here with a URL link to your written piece as a comment, for rewards.\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":""}],"memo":""}} +{"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/demo/boards","func":"CreateThread","args":["1","Getting Started","This is a demo of Gno smart contract programming. This document was\nconstructed by Gno onto a smart contract hosted on the data Realm\nname [\"gno.land/r/demo/boards\"](https://gno.land/r/demo/boards/)\n([github](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo/boards)).\n\n\n\n## Build `gnokey`, create your account, and interact with Gno.\n\nNOTE: Where you see `--remote %%REMOTE%%` here, that flag can be replaced\nwith `--remote localhost:26657` for local testnets.\n\n### Build `gnokey`.\n\n```bash\ngit clone git@github.com:gnolang/gno.git\ncd ./gno\nmake\n```\n\n### Generate a seed/mnemonic code.\n\n```bash\n./build/gnokey generate\n```\n\nNOTE: You can generate 24 words with any good bip39 generator.\n\n### Create a new account using your mnemonic.\n\n```bash\n./build/gnokey add KEYNAME --recover\n```\n\nNOTE: `KEYNAME` is your key identifier, and should be changed.\n\n### Verify that you can see your account locally.\n\n```bash\n./build/gnokey list\n```\n\n## Interact with the blockchain:\n\n### Get your current balance, account number, and sequence number.\n\n```bash\n./build/gnokey query auth/accounts/ACCOUNT_ADDR --remote %%REMOTE%%\n```\n\nNOTE: you can retrieve your `ACCOUNT_ADDR` with `./build/gnokey list`.\n\n### Acquire testnet tokens using the official faucet.\n\nGo to https://gno.land/faucet\n\n### Create a board with a smart contract call.\n\nNOTE: `BOARDNAME` will be the slug of the board, and should be changed.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateBoard\" --args \"BOARDNAME\" --gas-fee \"1000000ugnot\" --gas-wanted \"2000000\" --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateBoard\n\nNext, query for the permanent board ID by querying (you need this to create a new post):\n\n```bash\n./build/gnokey query \"vm/qeval\" --data \"gno.land/r/demo/boards.GetBoardIDFromName(\\\"BOARDNAME\\\")\" --remote %%REMOTE%%\n```\n\n### Create a post of a board with a smart contract call.\n\nNOTE: If a board was created successfully, your SEQUENCE_NUMBER would have increased.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateThread\" --args BOARD_ID --args \"Hello gno.land\" --args\\#file \"./examples/gno.land/r/demo/boards/example_post.md\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateThread\n\n### Create a comment to a post.\n\n```bash\n./build/gnokey maketx call KEYNAME --pkgpath \"gno.land/r/demo/boards\" --func \"CreateReply\" --args \"BOARD_ID\" --args \"1\" --args \"1\" --args \"Nice to meet you too.\" --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote %%REMOTE%%\n```\n\nInteractive documentation: https://gno.land/r/demo/boards$help\u0026func=CreateReply\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:BOARDNAME/1\" --remote %%REMOTE%%\n```\n\n### Render page with optional path expression.\n\nThe contents of `https://gno.land/r/demo/boards:` and `https://gno.land/r/demo/boards:gnolang` are rendered by calling\nthe `Render(path string)` function like so:\n\n```bash\n./build/gnokey query \"vm/qrender\" --data \"gno.land/r/demo/boards:gnolang\"\n```\n\n## Starting a local `gnoland` node:\n\n### Add test account.\n\n```bash\n./build/gnokey add test1 --recover\n```\n\nUse this mneonic:\n\u003e source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast\n\n### Start `gnoland` node.\n\n```bash\n./build/gnoland\n```\n\nNOTE: This can be reset with `make reset`\n\n### Publish the \"gno.land/p/demo/avl\" package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/p/demo/avl\" --pkgdir \"examples/gno.land/p/demo/avl\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n\n### Publish the \"gno.land/r/demo/boards\" realm package.\n\n```bash\n./build/gnokey maketx addpkg test1 --pkgpath \"gno.land/r/demo/boards\" --pkgdir \"examples/gno.land/r/demo/boards\" --deposit 100000000ugnot --gas-fee 1000000ugnot --gas-wanted 300000000 --broadcast=true --chainid %%CHAINID%% --remote localhost:26657\n```\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":""}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post1","First post","Lorem Ipsum","2022-05-20T13:17:22Z","","tag1,tag2"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} {"tx": {"msg":[{"@type":"/vm.m_call","caller":"g1manfred47kzduec920z88wfr64ylksmdcedlf5","send":"","pkg_path":"gno.land/r/gnoland/blog","func":"ModAddPost","args":["post2","Second post","Lorem Ipsum","2022-05-20T13:17:23Z","","tag1,tag3"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AnK+a6mcFDjY6b/v6p7r8QFW1M1PgIoQxBgrwOoyY7v3"},"signature":"sHjOGXZEi9wt2FSXFHmkDDoVQyepvFHKRDDU0zgedHYnCYPx5/YndyihsDD5Y2Z7/RgNYBh4JlJwDMGFNStzBQ=="}],"memo":""}} \ No newline at end of file diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 0826071b9f5..0b6eae25393 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -96,18 +96,22 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { baseApp.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, cfg.DB) // Construct keepers. - paramsKpr := params.NewParamsKeeper(mainKey, "vm") - acctKpr := auth.NewAccountKeeper(mainKey, paramsKpr, ProtoGnoAccount) - gpKpr := auth.NewGasPriceKeeper(mainKey) - bankKpr := bank.NewBankKeeper(acctKpr) - vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, paramsKpr) + prmk := params.NewParamsKeeper(mainKey) + acck := auth.NewAccountKeeper(mainKey, prmk.ForModule(auth.ModuleName), ProtoGnoAccount) + bankk := bank.NewBankKeeper(acck, prmk.ForModule(bank.ModuleName)) + gpk := auth.NewGasPriceKeeper(mainKey) + vmk := vm.NewVMKeeper(baseKey, mainKey, acck, bankk, prmk) vmk.Output = cfg.VMOutput + prmk.Register(auth.ModuleName, acck) + prmk.Register(bank.ModuleName, bankk) + prmk.Register(vm.ModuleName, vmk) + // Set InitChainer icc := cfg.InitChainerConfig icc.baseApp = baseApp - icc.acctKpr, icc.bankKpr, icc.vmKpr, icc.paramsKpr, icc.gpKpr = acctKpr, bankKpr, vmk, paramsKpr, gpKpr + icc.acck, icc.bankk, icc.vmk, icc.prmk, icc.gpk = acck, bankk, vmk, prmk, gpk baseApp.SetInitChainer(icc.InitChainer) // Set AnteHandler @@ -115,17 +119,16 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { VerifyGenesisSignatures: !cfg.SkipGenesisVerification, } authAnteHandler := auth.NewAnteHandler( - acctKpr, bankKpr, auth.DefaultSigVerificationGasConsumer, authOptions) + acck, bankk, auth.DefaultSigVerificationGasConsumer, authOptions) baseApp.SetAnteHandler( // Override default AnteHandler with custom logic. func(ctx sdk.Context, tx std.Tx, simulate bool) ( newCtx sdk.Context, res sdk.Result, abort bool, ) { // Add last gas price in the context - ctx = ctx.WithValue(auth.GasPriceContextKey{}, gpKpr.LastGasPrice(ctx)) - + ctx = ctx.WithValue(auth.GasPriceContextKey{}, gpk.LastGasPrice(ctx)) // Override auth params. - ctx = ctx.WithValue(auth.AuthParamsContextKey{}, acctKpr.GetParams(ctx)) + ctx = ctx.WithValue(auth.AuthParamsContextKey{}, acck.GetParams(ctx)) // Continue on with default auth ante handler. newCtx, res, abort = authAnteHandler(ctx, tx, simulate) return @@ -156,17 +159,17 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { baseApp.SetEndBlocker( EndBlocker( c, - acctKpr, - gpKpr, + acck, + gpk, vmk, baseApp, ), ) // Set a handler Route. - baseApp.Router().AddRoute("auth", auth.NewHandler(acctKpr)) - baseApp.Router().AddRoute("bank", bank.NewHandler(bankKpr)) - baseApp.Router().AddRoute("params", params.NewHandler(paramsKpr)) + baseApp.Router().AddRoute("auth", auth.NewHandler(acck)) + baseApp.Router().AddRoute("bank", bank.NewHandler(bankk)) + baseApp.Router().AddRoute("params", params.NewHandler(prmk)) baseApp.Router().AddRoute("vm", vm.NewHandler(vmk)) // Load latest version. @@ -262,12 +265,12 @@ type InitChainerConfig struct { // These fields are passed directly by NewAppWithOptions, and should not be // configurable by end-users. - baseApp *sdk.BaseApp - vmKpr vm.VMKeeperI - acctKpr auth.AccountKeeperI - bankKpr bank.BankKeeperI - paramsKpr params.ParamsKeeperI - gpKpr auth.GasPriceKeeperI + baseApp *sdk.BaseApp + vmk vm.VMKeeperI + acck auth.AccountKeeperI + bankk bank.BankKeeperI + prmk params.ParamsKeeperI + gpk auth.GasPriceKeeperI } // InitChainer is the function that can be used as a [sdk.InitChainer]. @@ -308,14 +311,14 @@ func (cfg InitChainerConfig) loadStdlibs(ctx sdk.Context) { ms := ctx.MultiStore() msCache := ms.MultiCacheWrap() - stdlibCtx := cfg.vmKpr.MakeGnoTransactionStore(ctx) + stdlibCtx := cfg.vmk.MakeGnoTransactionStore(ctx) stdlibCtx = stdlibCtx.WithMultiStore(msCache) if cfg.CacheStdlibLoad { - cfg.vmKpr.LoadStdlibCached(stdlibCtx, cfg.StdlibDir) + cfg.vmk.LoadStdlibCached(stdlibCtx, cfg.StdlibDir) } else { - cfg.vmKpr.LoadStdlib(stdlibCtx, cfg.StdlibDir) + cfg.vmk.LoadStdlib(stdlibCtx, cfg.StdlibDir) } - cfg.vmKpr.CommitGnoTransactionStore(stdlibCtx) + cfg.vmk.CommitGnoTransactionStore(stdlibCtx) msCache.MultiWrite() } @@ -325,26 +328,37 @@ func (cfg InitChainerConfig) loadAppState(ctx sdk.Context, appState any) ([]abci if !ok { return nil, fmt.Errorf("invalid AppState of type %T", appState) } - cfg.acctKpr.InitGenesis(ctx, state.Auth) - params := cfg.acctKpr.GetParams(ctx) - ctx = ctx.WithValue(auth.AuthParamsContextKey{}, params) - auth.InitChainer(ctx, cfg.gpKpr.(auth.GasPriceKeeper), params.InitialGasPrice) + cfg.bankk.InitGenesis(ctx, state.Bank) // Apply genesis balances. for _, bal := range state.Balances { - acc := cfg.acctKpr.NewAccountWithAddress(ctx, bal.Address) - cfg.acctKpr.SetAccount(ctx, acc) - err := cfg.bankKpr.SetCoins(ctx, bal.Address, bal.Amount) + acc := cfg.acck.NewAccountWithAddress(ctx, bal.Address) + cfg.acck.SetAccount(ctx, acc) + err := cfg.bankk.SetCoins(ctx, bal.Address, bal.Amount) if err != nil { panic(err) } } - - // Apply genesis params. - for _, param := range state.Params { - param.register(ctx, cfg.paramsKpr) + // The account keeper's initial genesis state must be set after genesis + // accounts are created in account keeeper with genesis balances + cfg.acck.InitGenesis(ctx, state.Auth) + + // The unrestricted address must have been created as one of the genesis accounts. + // Otherwise, we cannot verify the unrestricted address in the genesis state. + + for _, addr := range state.Auth.Params.UnrestrictedAddrs { + acc := cfg.acck.GetAccount(ctx, addr) + accr := acc.(*GnoAccount) + accr.SetUnrestricted() + cfg.acck.SetAccount(ctx, acc) } + cfg.vmk.InitGenesis(ctx, state.VM) + + params := cfg.acck.GetParams(ctx) + ctx = ctx.WithValue(auth.AuthParamsContextKey{}, params) + auth.InitChainer(ctx, cfg.gpk, params.InitialGasPrice) + // Replay genesis txs. txResponses := make([]abci.ResponseDeliverTx, 0, len(state.Txs)) @@ -406,8 +420,8 @@ type endBlockerApp interface { // validator set changes func EndBlocker( collector *collector[validatorUpdate], - acctKpr auth.AccountKeeperI, - gpKpr auth.GasPriceKeeperI, + acck auth.AccountKeeperI, + gpk auth.GasPriceKeeperI, vmk vm.VMKeeperI, app endBlockerApp, ) func( @@ -417,11 +431,11 @@ func EndBlocker( return func(ctx sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock { // set the auth params value in the ctx. The EndBlocker will use InitialGasPrice in // the params to calculate the updated gas price. - if acctKpr != nil { - ctx = ctx.WithValue(auth.AuthParamsContextKey{}, acctKpr.GetParams(ctx)) + if acck != nil { + ctx = ctx.WithValue(auth.AuthParamsContextKey{}, acck.GetParams(ctx)) } - if acctKpr != nil && gpKpr != nil { - auth.EndBlocker(ctx, gpKpr) + if acck != nil && gpk != nil { + auth.EndBlocker(ctx, gpk) } // Check if there was a valset change if len(collector.getEvents()) == 0 { diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index 361d7505157..8ca6d9b9476 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -64,12 +64,13 @@ func TestNewAppWithOptions(t *testing.T) { }, }, } - appState.Params = []Param{ - {key: "foo", kind: "string", value: "hello"}, - {key: "foo", kind: "int64", value: int64(-42)}, - {key: "foo", kind: "uint64", value: uint64(1337)}, - {key: "foo", kind: "bool", value: true}, - {key: "foo", kind: "bytes", value: []byte{0x48, 0x69, 0x21}}, + appState.VM.RealmParams = []params.Param{ + params.NewParam("gno.land/r/sys/testrealm:bar_string", "hello"), + params.NewParam("gno.land/r/sys/testrealm:bar_int64", int64(-42)), + params.NewParam("gno.land/r/sys/testrealm:bar_uint64", uint64(1337)), + params.NewParam("gno.land/r/sys/testrealm:bar_bool", true), + params.NewParam("gno.land/r/sys/testrealm:bar_strings", []string{"some", "strings"}), + params.NewParam("gno.land/r/sys/testrealm:bar_bytes", []byte{0x48, 0x69, 0x21}), } resp := bapp.InitChain(abci.RequestInitChain{ @@ -108,12 +109,14 @@ func TestNewAppWithOptions(t *testing.T) { path string expectedVal string }{ - {"params/vm/foo.string", `"hello"`}, - {"params/vm/foo.int64", `"-42"`}, - {"params/vm/foo.uint64", `"1337"`}, - {"params/vm/foo.bool", `true`}, - {"params/vm/foo.bytes", `"SGkh"`}, // XXX: make this test more readable + {"params/vm:gno.land/r/sys/testrealm:bar_string", `"hello"`}, + {"params/vm:gno.land/r/sys/testrealm:bar_int64", `"-42"`}, + {"params/vm:gno.land/r/sys/testrealm:bar_uint64", `"1337"`}, + {"params/vm:gno.land/r/sys/testrealm:bar_bool", `true`}, + {"params/vm:gno.land/r/sys/testrealm:bar_strings", `["some","strings"]`}, + {"params/vm:gno.land/r/sys/testrealm:bar_bytes", `"SGkh"`}, // XXX: make this test more readable } + for _, tc := range tcs { qres := bapp.Query(abci.RequestQuery{ Path: tc.path, @@ -215,13 +218,14 @@ func testInitChainerLoadStdlib(t *testing.T, cached bool) { //nolint:thelper // call initchainer cfg := InitChainerConfig{ StdlibDir: stdlibDir, - vmKpr: mock, + vmk: mock, + acck: &mockAuthKeeper{}, + bankk: &mockBankKeeper{}, + prmk: &mockParamsKeeper{}, + gpk: &mockGasPriceKeeper{}, CacheStdlibLoad: cached, } - // Construct keepers. - paramsKpr := params.NewParamsKeeper(iavlCapKey, "") - cfg.acctKpr = auth.NewAccountKeeper(iavlCapKey, paramsKpr, ProtoGnoAccount) - cfg.gpKpr = auth.NewGasPriceKeeper(iavlCapKey) + cfg.InitChainer(testCtx, abci.RequestInitChain{ AppState: DefaultGenState(), }) @@ -311,6 +315,9 @@ func TestInitChainer_MetadataTxs(t *testing.T) { }, // Make sure the deployer account has a balance Balances: balances, + Auth: auth.DefaultGenesisState(), + Bank: bank.DefaultGenesisState(), + VM: vm.DefaultGenesisState(), } } @@ -322,6 +329,9 @@ func TestInitChainer_MetadataTxs(t *testing.T) { }, }, Balances: balances, + Auth: auth.DefaultGenesisState(), + Bank: bank.DefaultGenesisState(), + VM: vm.DefaultGenesisState(), } } ) @@ -822,16 +832,18 @@ func newGasPriceTestApp(t *testing.T) abci.Application { baseApp.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, cfg.DB) // Construct keepers. - paramsKpr := params.NewParamsKeeper(mainKey, "") - acctKpr := auth.NewAccountKeeper(mainKey, paramsKpr, ProtoGnoAccount) - gpKpr := auth.NewGasPriceKeeper(mainKey) - bankKpr := bank.NewBankKeeper(acctKpr) - vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, paramsKpr) - + prmk := params.NewParamsKeeper(mainKey) + acck := auth.NewAccountKeeper(mainKey, prmk.ForModule(auth.ModuleName), ProtoGnoAccount) + gpk := auth.NewGasPriceKeeper(mainKey) + bankk := bank.NewBankKeeper(acck, prmk.ForModule(bank.ModuleName)) + vmk := vm.NewVMKeeper(baseKey, mainKey, acck, bankk, prmk) + prmk.Register(auth.ModuleName, acck) + prmk.Register(bank.ModuleName, bankk) + prmk.Register(vm.ModuleName, vmk) // Set InitChainer icc := cfg.InitChainerConfig icc.baseApp = baseApp - icc.acctKpr, icc.bankKpr, icc.vmKpr, icc.gpKpr = acctKpr, bankKpr, vmk, gpKpr + icc.acck, icc.bankk, icc.vmk, icc.gpk = acck, bankk, vmk, gpk baseApp.SetInitChainer(icc.InitChainer) // Set AnteHandler @@ -841,10 +853,10 @@ func newGasPriceTestApp(t *testing.T) abci.Application { newCtx sdk.Context, res sdk.Result, abort bool, ) { // Add last gas price in the context - ctx = ctx.WithValue(auth.GasPriceContextKey{}, gpKpr.LastGasPrice(ctx)) + ctx = ctx.WithValue(auth.GasPriceContextKey{}, gpk.LastGasPrice(ctx)) // Override auth params. - ctx = ctx.WithValue(auth.AuthParamsContextKey{}, acctKpr.GetParams(ctx)) + ctx = ctx.WithValue(auth.AuthParamsContextKey{}, acck.GetParams(ctx)) // Continue on with default auth ante handler. if ctx.IsCheckTx() { res := auth.EnsureSufficientMempoolFees(ctx, tx.Fee) @@ -853,7 +865,7 @@ func newGasPriceTestApp(t *testing.T) abci.Application { } } - newCtx = auth.SetGasMeter(false, ctx, tx.Fee.GasWanted) + newCtx = auth.SetGasMeter(ctx, tx.Fee.GasWanted) count := getTotalCount(tx) @@ -875,16 +887,16 @@ func newGasPriceTestApp(t *testing.T) abci.Application { baseApp.SetEndBlocker( EndBlocker( c, - acctKpr, - gpKpr, + acck, + gpk, nil, baseApp, ), ) // Set a handler Route. - baseApp.Router().AddRoute("auth", auth.NewHandler(acctKpr)) - baseApp.Router().AddRoute("bank", bank.NewHandler(bankKpr)) + baseApp.Router().AddRoute("auth", auth.NewHandler(acck)) + baseApp.Router().AddRoute("bank", bank.NewHandler(bankk)) baseApp.Router().AddRoute( testutils.RouteMsgCounter, newTestHandler( @@ -950,6 +962,10 @@ func gnoGenesisState(t *testing.T) GnoGenesisState { } }`) err := amino.UnmarshalJSON(genBytes, &gen) + + gen.Bank = bank.DefaultGenesisState() + gen.VM = vm.DefaultGenesisState() + if err != nil { t.Fatalf("failed to create genesis state: %v", err) } diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go index a754e7a4644..f80816e4423 100644 --- a/gno.land/pkg/gnoland/genesis.go +++ b/gno.land/pkg/gnoland/genesis.go @@ -13,6 +13,8 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/sdk/params" "github.com/gnolang/gno/tm2/pkg/std" "github.com/pelletier/go-toml" ) @@ -62,43 +64,81 @@ func LoadGenesisBalancesFile(path string) (Balances, error) { return balances, nil } +func splitTypedName(typedName string) (name string, type_ string) { + parts := strings.Split(typedName, ".") + if len(parts) == 1 { + return typedName, "" + } else if len(parts) == 2 { + return parts[0], parts[1] + } else { + panic("malforumed typed name: expected or . but got " + typedName) + } +} + // LoadGenesisParamsFile loads genesis params from the provided file path. -func LoadGenesisParamsFile(path string) ([]Param, error) { - // each param is in the form: key.kind=value +func LoadGenesisParamsFile(path string, ggs *GnoGenesisState) error { content, err := osm.ReadFile(path) if err != nil { - return nil, err + return err } - m := map[string] /*category*/ map[string] /*key*/ map[string] /*kind*/ interface{} /*value*/ {} + // Parameters are grouped by modules (or more specifically module:submodule). + // The vm module uses the submodule for realm package paths. + // If only the module is specified, the submodule is assumed to be "p" + // for keeper param structs. + m := map[string] /* (:)? */ map[string] /* */ interface{} /* */ {} err = toml.Unmarshal(content, &m) if err != nil { - return nil, err + return err } - params := make([]Param, 0) - for category, keys := range m { - for key, kinds := range keys { - for kind, val := range kinds { - param := Param{ - key: category + "." + key, - kind: kind, - } - switch kind { - case "uint64": // toml - param.value = uint64(val.(int64)) - default: - param.value = val - } - if err := param.Verify(); err != nil { - return nil, err - } - params = append(params, param) + // XXX Write onto ggs for other keeper params. + + // Write onto ggs.VM.Params. + if vmparams, ok := m["vm"]; ok { + for name, value := range vmparams { + name, _ := splitTypedName(name) + switch name { + case "chain_domain": + ggs.VM.Params.ChainDomain = value.(string) + case "sysnames_pkgpath": + ggs.VM.Params.SysNamesPkgPath = value.(string) + default: + return errors.New("unexpected vm parameter " + name) } } } - return params, nil + // Write onto ggs.VM.RealmParams. + for modrlm, values := range m { + if !strings.HasPrefix(modrlm, "vm:") { + continue + } + parts := strings.Split(modrlm, ":") + numparts := len(parts) + if numparts == 1 { + // keeper param struct (sys param). skip + } else if numparts == 2 { + realm := parts[1] + // XXX validate realm part. + for name, value := range values { + name, type_ := splitTypedName(name) + if type_ == "strings" { + vz := value.([]interface{}) + sz := make([]string, len(vz)) + for i, v := range vz { + sz[i] = v.(string) + } + value = sz + } + param := params.NewParam(realm+":"+name, value) + ggs.VM.RealmParams = append(ggs.VM.RealmParams, param) + } + } else { + return errors.New("invalid key " + modrlm + ", expected format ::") + } + } + return nil } // LoadGenesisTxsFile loads genesis transactions from the provided file path. @@ -200,7 +240,8 @@ func DefaultGenState() GnoGenesisState { Balances: []Balance{}, Txs: []TxWithMetadata{}, Auth: authGen, + Bank: bank.DefaultGenesisState(), + VM: vmm.DefaultGenesisState(), } - return gs } diff --git a/gno.land/pkg/gnoland/mock_test.go b/gno.land/pkg/gnoland/mock_test.go index 62aecaf5278..e8dad52391f 100644 --- a/gno.land/pkg/gnoland/mock_test.go +++ b/gno.land/pkg/gnoland/mock_test.go @@ -4,10 +4,15 @@ import ( "log/slog" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "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/sdk" + "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/service" + "github.com/gnolang/gno/tm2/pkg/std" ) type ( @@ -113,6 +118,85 @@ func (m *mockVMKeeper) CommitGnoTransactionStore(ctx sdk.Context) { } } +func (m *mockVMKeeper) InitGenesis(ctx sdk.Context, gs vm.GenesisState) {} + +type mockBankKeeper struct{} + +func (m *mockBankKeeper) InputOutputCoins(ctx sdk.Context, inputs []bank.Input, outputs []bank.Output) error { + return nil +} + +func (m *mockBankKeeper) SendCoins(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { + return nil +} + +func (m *mockBankKeeper) SendCoinsUnrestricted(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { + return nil +} + +func (m *mockBankKeeper) SubtractCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) (std.Coins, error) { + return nil, nil +} + +func (m *mockBankKeeper) AddCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) (std.Coins, error) { + return nil, nil +} + +func (m *mockBankKeeper) InitGenesis(ctx sdk.Context, data bank.GenesisState) {} +func (m *mockBankKeeper) GetParams(ctx sdk.Context) bank.Params { return bank.Params{} } +func (m *mockBankKeeper) GetCoins(ctx sdk.Context, addr crypto.Address) std.Coins { return nil } +func (m *mockBankKeeper) SetCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) error { + return nil +} + +func (m *mockBankKeeper) HasCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) bool { + return true +} + +type mockAuthKeeper struct{} + +func (m *mockAuthKeeper) NewAccountWithAddress(ctx sdk.Context, addr crypto.Address) std.Account { + return nil +} +func (m *mockAuthKeeper) GetAccount(ctx sdk.Context, addr crypto.Address) std.Account { return nil } +func (m *mockAuthKeeper) GetAllAccounts(ctx sdk.Context) []std.Account { return nil } +func (m *mockAuthKeeper) SetAccount(ctx sdk.Context, acc std.Account) {} +func (m *mockAuthKeeper) IterateAccounts(ctx sdk.Context, process func(std.Account) bool) {} +func (m *mockAuthKeeper) InitGenesis(ctx sdk.Context, data auth.GenesisState) {} +func (m *mockAuthKeeper) GetParams(ctx sdk.Context) auth.Params { return auth.Params{} } + +type mockParamsKeeper struct{} + +func (m *mockParamsKeeper) GetString(ctx sdk.Context, key string, ptr *string) {} +func (m *mockParamsKeeper) GetInt64(ctx sdk.Context, key string, ptr *int64) {} +func (m *mockParamsKeeper) GetUint64(ctx sdk.Context, key string, ptr *uint64) {} +func (m *mockParamsKeeper) GetBool(ctx sdk.Context, key string, ptr *bool) {} +func (m *mockParamsKeeper) GetBytes(ctx sdk.Context, key string, ptr *[]byte) {} +func (m *mockParamsKeeper) GetStrings(ctx sdk.Context, key string, ptr *[]string) {} + +func (m *mockParamsKeeper) SetString(ctx sdk.Context, key string, value string) {} +func (m *mockParamsKeeper) SetInt64(ctx sdk.Context, key string, value int64) {} +func (m *mockParamsKeeper) SetUint64(ctx sdk.Context, key string, value uint64) {} +func (m *mockParamsKeeper) SetBool(ctx sdk.Context, key string, value bool) {} +func (m *mockParamsKeeper) SetBytes(ctx sdk.Context, key string, value []byte) {} +func (m *mockParamsKeeper) SetStrings(ctx sdk.Context, key string, value []string) {} + +func (m *mockParamsKeeper) Has(ctx sdk.Context, key string) bool { return false } +func (m *mockParamsKeeper) GetRaw(ctx sdk.Context, key string) []byte { return nil } +func (m *mockParamsKeeper) SetRaw(ctx sdk.Context, key string, value []byte) {} + +func (m *mockParamsKeeper) GetStruct(ctx sdk.Context, key string, strctPtr interface{}) {} +func (m *mockParamsKeeper) SetStruct(ctx sdk.Context, key string, strct interface{}) {} + +func (m *mockParamsKeeper) GetAny(ctx sdk.Context, key string) interface{} { return nil } +func (m *mockParamsKeeper) SetAny(ctx sdk.Context, key string, value interface{}) {} + +type mockGasPriceKeeper struct{} + +func (m *mockGasPriceKeeper) LastGasPrice(ctx sdk.Context) std.GasPrice { return std.GasPrice{} } +func (m *mockGasPriceKeeper) SetGasPrice(ctx sdk.Context, gp std.GasPrice) {} +func (m *mockGasPriceKeeper) UpdateGasPrice(ctx sdk.Context) {} + type ( lastBlockHeightDelegate func() int64 loggerDelegate func() *slog.Logger diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index cc9e74a78d8..346162bc695 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -7,6 +7,7 @@ import ( "path/filepath" "time" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" @@ -38,10 +39,6 @@ func NewMockedPrivValidator() bft.PrivValidator { // NewDefaultGenesisConfig creates a default configuration for an in-memory node. func NewDefaultGenesisConfig(chainid, chaindomain string) *bft.GenesisDoc { - // custom chain domain - var domainParam Param - _ = domainParam.Parse("gno.land/r/sys/params.vm.chain_domain.string=" + chaindomain) - return &bft.GenesisDoc{ GenesisTime: time.Now(), ChainID: chainid, @@ -51,8 +48,10 @@ func NewDefaultGenesisConfig(chainid, chaindomain string) *bft.GenesisDoc { AppState: &GnoGenesisState{ Balances: []Balance{}, Txs: []TxWithMetadata{}, - Params: []Param{ - domainParam, + VM: vm.GenesisState{ + Params: vm.Params{ + ChainDomain: chaindomain, + }, }, }, } @@ -60,10 +59,10 @@ func NewDefaultGenesisConfig(chainid, chaindomain string) *bft.GenesisDoc { func defaultBlockParams() *abci.BlockParams { return &abci.BlockParams{ - MaxTxBytes: 1_000_000, // 1MB, - MaxDataBytes: 2_000_000, // 2MB, - MaxGas: 100_000_000, // 100M gas - TimeIotaMS: 100, // 100ms + MaxTxBytes: 1_000_000, // 1MB, + MaxDataBytes: 2_000_000, // 2MB, + MaxGas: 3_000_000_000, // 3B gas + TimeIotaMS: 100, // 100ms } } diff --git a/gno.land/pkg/gnoland/param.go b/gno.land/pkg/gnoland/param.go deleted file mode 100644 index 4c1e1190751..00000000000 --- a/gno.land/pkg/gnoland/param.go +++ /dev/null @@ -1,121 +0,0 @@ -package gnoland - -import ( - "encoding/hex" - "errors" - "fmt" - "strconv" - "strings" - - "github.com/gnolang/gno/tm2/pkg/sdk" - "github.com/gnolang/gno/tm2/pkg/sdk/params" -) - -type Param struct { - key string - kind string - value interface{} -} - -func (p Param) Verify() error { - // XXX: validate - return nil -} - -const ( - ParamKindString = "string" - ParamKindInt64 = "int64" - ParamKindUint64 = "uint64" - ParamKindBool = "bool" - ParamKindBytes = "bytes" -) - -func (p *Param) Parse(entry string) error { - parts := strings.SplitN(strings.TrimSpace(entry), "=", 2) // .= - if len(parts) != 2 { - return fmt.Errorf("malformed entry: %q", entry) - } - - keyWithKind := parts[0] - rawValue := parts[1] - p.kind = keyWithKind[strings.LastIndex(keyWithKind, ".")+1:] - p.key = strings.TrimSuffix(keyWithKind, "."+p.kind) - switch p.kind { - case ParamKindString: - p.value = rawValue - case ParamKindInt64: - v, err := strconv.ParseInt(rawValue, 10, 64) - if err != nil { - return err - } - p.value = v - case ParamKindBool: - v, err := strconv.ParseBool(rawValue) - if err != nil { - return err - } - p.value = v - case ParamKindUint64: - v, err := strconv.ParseUint(rawValue, 10, 64) - if err != nil { - return err - } - p.value = v - case ParamKindBytes: - v, err := hex.DecodeString(rawValue) - if err != nil { - return err - } - p.value = v - default: - return errors.New("unsupported param kind: " + p.kind + " (" + entry + ")") - } - - return p.Verify() -} - -func (p Param) String() string { - typedKey := p.key + "." + p.kind - switch p.kind { - case ParamKindString: - return fmt.Sprintf("%s=%s", typedKey, p.value) - case ParamKindInt64: - return fmt.Sprintf("%s=%d", typedKey, p.value) - case ParamKindUint64: - return fmt.Sprintf("%s=%d", typedKey, p.value) - case ParamKindBool: - if p.value.(bool) { - return fmt.Sprintf("%s=true", typedKey) - } - return fmt.Sprintf("%s=false", typedKey) - case ParamKindBytes: - return fmt.Sprintf("%s=%x", typedKey, p.value) - } - panic("invalid param kind:" + p.kind) -} - -func (p *Param) UnmarshalAmino(rep string) error { - return p.Parse(rep) -} - -func (p Param) MarshalAmino() (string, error) { - return p.String(), nil -} - -func (p Param) register(ctx sdk.Context, prk params.ParamsKeeperI) { - key := p.key + "." + p.kind - switch p.kind { - case ParamKindString: - prk.SetString(ctx, key, p.value.(string)) - case ParamKindInt64: - prk.SetInt64(ctx, key, p.value.(int64)) - case ParamKindUint64: - prk.SetUint64(ctx, key, p.value.(uint64)) - case ParamKindBool: - prk.SetBool(ctx, key, p.value.(bool)) - case ParamKindBytes: - prk.SetBytes(ctx, key, p.value.([]byte)) - default: - panic("invalid param kind: " + p.kind) - } -} diff --git a/gno.land/pkg/gnoland/test_common.go b/gno.land/pkg/gnoland/test_common.go new file mode 100644 index 00000000000..eac143a2a08 --- /dev/null +++ b/gno.land/pkg/gnoland/test_common.go @@ -0,0 +1,53 @@ +package gnoland + +import ( + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/db/memdb" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" + "github.com/gnolang/gno/tm2/pkg/sdk/params" + + "github.com/gnolang/gno/tm2/pkg/store" + "github.com/gnolang/gno/tm2/pkg/store/iavl" +) + +type testEnv struct { + ctx sdk.Context + acck auth.AccountKeeper + bankk bank.BankKeeper +} + +func setupTestEnv() testEnv { + db := memdb.NewMemDB() + + authCapKey := store.NewStoreKey("authCapKey") + + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(authCapKey, iavl.StoreConstructor, db) + ms.LoadLatestVersion() + prmk := params.NewParamsKeeper(authCapKey) + acck := auth.NewAccountKeeper(authCapKey, prmk.ForModule(auth.ModuleName), ProtoGnoAccount) + bankk := bank.NewBankKeeper(acck, prmk.ForModule(bank.ModuleName)) + prmk.Register(auth.ModuleName, acck) + prmk.Register(bank.ModuleName, bankk) + + ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{Height: 1, ChainID: "test-chain-id"}, log.NewNoopLogger()) + + ctx = ctx.WithConsensusParams(&abci.ConsensusParams{ + Block: &abci.BlockParams{ + MaxTxBytes: 1024, + MaxDataBytes: 1024 * 100, + MaxBlockBytes: 1024 * 100, + MaxGas: 10 * 1000 * 1000, + TimeIotaMS: 10, + }, + Validator: &abci.ValidatorParams{ + PubKeyTypeURLs: []string{}, // XXX + }, + }) + + return testEnv{ctx: ctx, acck: acck, bankk: bankk} +} diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go index 66fb2f54e8a..2feb5d10a8b 100644 --- a/gno.land/pkg/gnoland/types.go +++ b/gno.land/pkg/gnoland/types.go @@ -7,9 +7,11 @@ import ( "fmt" "os" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -18,8 +20,82 @@ var ( ErrBalanceEmptyAmount = errors.New("balance amount is empty") ) +const ( + // XXX rename these to flagXyz. + + // flagUnrestricted allows flagUnrestricted transfers. + flagUnrestricted BitSet = 1 << iota + + // TODO: flagValidatorAccount marks an account as validator. + flagValidatorAccount + + // TODO: flagRealmAccount marks an account as realm. + flagRealmAccount +) + +// bitSet represents a set of flags stored in a 64-bit unsigned integer. +// Each bit in the BitSet corresponds to a specific flag. +type BitSet uint64 + +func (bs BitSet) String() string { + return fmt.Sprintf("0x%016X", uint64(bs)) // Show all 64 bits +} + +var _ std.AccountUnrestricter = &GnoAccount{} + type GnoAccount struct { std.BaseAccount + Attributes BitSet `json:"attributes" yaml:"attributes"` +} + +// validFlags defines the set of all valid flags that can be used with BitSet. +var validFlags = flagUnrestricted | flagValidatorAccount | flagRealmAccount + +func (ga *GnoAccount) setFlag(flag BitSet) { + if !isValidFlag(flag) { + panic(fmt.Sprintf("setFlag: invalid flag %d (binary: %b). Valid flags: %b", flag, flag, validFlags)) + } + ga.Attributes |= flag +} + +func (ga *GnoAccount) clearFlag(flag BitSet) { + if !isValidFlag(flag) { + panic(fmt.Sprintf("clearFlag: invalid flag %d (binary: %b). Valid flags: %b", flag, flag, validFlags)) + } + ga.Attributes &= ^flag +} + +func (ga *GnoAccount) hasFlag(flag BitSet) bool { + if !isValidFlag(flag) { + panic(fmt.Sprintf("hasFlag: invalid flag %d (binary: %b). Valid flags: %b", flag, flag, validFlags)) + } + return ga.Attributes&flag != 0 +} + +// isValidFlag ensures that a given BitSet uses only the allowed subset of bits +// as defined in validFlags. This prevents accidentally setting invalid flags, +// especially since BitSet can represent all 64 bits of a uint64. +func isValidFlag(flag BitSet) bool { + return flag&^validFlags == 0 && flag != 0 +} + +// SetUnrestricted allows the account to bypass global transfer locking restrictions. +// By default, accounts are restricted when global transfer locking is enabled. +func (ga *GnoAccount) SetUnrestricted() { + ga.setFlag(flagUnrestricted) +} + +// IsUnrestricted checks whether the account is flagUnrestricted. +func (ga *GnoAccount) IsUnrestricted() bool { + return ga.hasFlag(flagUnrestricted) +} + +// String implements fmt.Stringer +func (ga *GnoAccount) String() string { + return fmt.Sprintf("%s\n Attributes: %s", + ga.BaseAccount.String(), + ga.Attributes.String(), + ) } func ProtoGnoAccount() std.Account { @@ -29,8 +105,9 @@ func ProtoGnoAccount() std.Account { type GnoGenesisState struct { Balances []Balance `json:"balances"` Txs []TxWithMetadata `json:"txs"` - Params []Param `json:"params"` Auth auth.GenesisState `json:"auth"` + Bank bank.GenesisState `json:"bank"` + VM vm.GenesisState `json:"vm"` } type TxWithMetadata struct { diff --git a/gno.land/pkg/gnoland/types_test.go b/gno.land/pkg/gnoland/types_test.go index c501325bc3e..4d12f12d297 100644 --- a/gno.land/pkg/gnoland/types_test.go +++ b/gno.land/pkg/gnoland/types_test.go @@ -131,6 +131,98 @@ func TestReadGenesisTxs(t *testing.T) { }) } +func TestGnoAccountRestriction(t *testing.T) { + testEnv := setupTestEnv() + ctx, acck, bankk := testEnv.ctx, testEnv.acck, testEnv.bankk + + fromAddress := crypto.AddressFromPreimage([]byte("from")) + toAddress := crypto.AddressFromPreimage([]byte("to")) + fromAccount := acck.NewAccountWithAddress(ctx, fromAddress) + toAccount := acck.NewAccountWithAddress(ctx, toAddress) + + // Default account is not unrestricted + assert.False(t, fromAccount.(*GnoAccount).IsUnrestricted()) + + // Send Unrestricted + fromAccount.SetCoins(std.NewCoins(std.NewCoin("foocoin", 10))) + acck.SetAccount(ctx, fromAccount) + acck.SetAccount(ctx, toAccount) + + err := bankk.SendCoins(ctx, fromAddress, toAddress, std.NewCoins(std.NewCoin("foocoin", 3))) + require.NoError(t, err) + balance := acck.GetAccount(ctx, toAddress).GetCoins() + assert.Equal(t, balance.String(), "3foocoin") + + // Send Restricted + bankk.SetRestrictedDenoms(ctx, []string{"foocoin"}) + err = bankk.SendCoins(ctx, fromAddress, toAddress, std.NewCoins(std.NewCoin("foocoin", 3))) + require.Error(t, err) + assert.Equal(t, "restricted token transfer error", err.Error()) + + // Set unrestrict Account + fromAccount.(*GnoAccount).SetUnrestricted() + assert.True(t, fromAccount.(*GnoAccount).IsUnrestricted()) + + // Persisted unrestricted state + acck.SetAccount(ctx, fromAccount) + fromAccount = acck.GetAccount(ctx, fromAddress) + assert.True(t, fromAccount.(*GnoAccount).IsUnrestricted()) + + // Send Restricted + bankk.SetRestrictedDenoms(ctx, []string{"foocoin"}) // XXX unnecessary? + err = bankk.SendCoins(ctx, fromAddress, toAddress, std.NewCoins(std.NewCoin("foocoin", 3))) + require.NoError(t, err) + assert.Equal(t, balance.String(), "3foocoin") +} + +func TestGnoAccountSendRestrictions(t *testing.T) { + testEnv := setupTestEnv() + ctx, acck, bankk := testEnv.ctx, testEnv.acck, testEnv.bankk + + bankk.SetRestrictedDenoms(ctx, []string{"foocoin"}) + addr := crypto.AddressFromPreimage([]byte("addr1")) + addr2 := crypto.AddressFromPreimage([]byte("addr2")) + acc := acck.NewAccountWithAddress(ctx, addr) + + // All accounts are restricted by default when the transfer restriction is applied. + + // Test GetCoins/SetCoins + acck.SetAccount(ctx, acc) + require.True(t, bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins())) + + bankk.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10))) + require.True(t, bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) + + // Test HasCoins + require.True(t, bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10)))) + require.True(t, bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 5)))) + require.False(t, bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15)))) + require.False(t, bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 5)))) + + bankk.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15))) + + // Test sending coins restricted to locked accounts. + err := bankk.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 5))) + require.ErrorIs(t, err, std.RestrictedTransferError{}, "expected restricted transfer error, got %v", err) + require.True(t, bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 15)))) + require.True(t, bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("foocoin", 0)))) + + // Test sending coins unrestricted to locked accounts. + bankk.AddCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 30))) + err = bankk.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("barcoin", 10))) + require.NoError(t, err) + require.True(t, bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 20), std.NewCoin("foocoin", 15)))) + require.True(t, bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10)))) + + // Remove the restrictions + bankk.SetRestrictedDenoms(ctx, []string{}) + // Test sending coins restricted to locked accounts. + err = bankk.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 5))) + require.NoError(t, err) + require.True(t, bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 20), std.NewCoin("foocoin", 10)))) + require.True(t, bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 5)))) +} + func TestSignGenesisTx(t *testing.T) { t.Parallel() @@ -156,3 +248,27 @@ func TestSignGenesisTx(t *testing.T) { assert.True(t, pubKey.VerifyBytes(payload, sigs[0].Signature)) } } + +func TestSetFlag(t *testing.T) { + account := &GnoAccount{} + + // Test setting a valid flag + account.setFlag(flagUnrestricted) + assert.True(t, account.hasFlag(flagUnrestricted), "Expected unrestricted flag to be set") + + // Test setting an invalid flag + assert.Panics(t, func() { + account.setFlag(BitSet(0x1000)) // Invalid flag + }, "Expected panic for invalid flag") +} + +func TestClearFlag(t *testing.T) { + account := &GnoAccount{} + + // Set and then clear the flag + account.setFlag(flagUnrestricted) + assert.True(t, account.hasFlag(flagUnrestricted), "Expected unrestricted flag to be set before clearing") + + account.clearFlag(flagUnrestricted) + assert.False(t, account.hasFlag(flagUnrestricted), "Expected unrestricted flag to be cleared") +} diff --git a/gno.land/pkg/gnoweb/Makefile b/gno.land/pkg/gnoweb/Makefile index 39c9d20ab10..be7c2c2a3a2 100644 --- a/gno.land/pkg/gnoweb/Makefile +++ b/gno.land/pkg/gnoweb/Makefile @@ -13,7 +13,7 @@ input_css := frontend/css/input.css output_css := $(PUBLIC_DIR)/styles.css tw_version := 3.4.14 tw_config_path := frontend/css/tx.config.js -templates_files := $(shell find . -iname '*.gohtml') +templates_files := $(shell find . -iname '*.html') # static config src_dir_static := frontend/static @@ -39,6 +39,9 @@ cache_dir := .cache # Install dependencies all: generate +test: + go test -v ./... + # Generate process generate: css ts static @@ -76,8 +79,9 @@ dev: # Go server in development mode dev.gnoweb: generate - $(run_reflex) -s -r '.*\.go(html)?' -- \ - go run ../../cmd/gnoweb -assets-dir=${PUBLIC_DIR} -chainid=${CHAIN_ID} -remote=${DEV_REMOTE} \ + $(run_reflex) -s -r '.*\.(go|html)' -- \ + go run ../../cmd/gnoweb -no-strict -assets-dir=${PUBLIC_DIR} -chainid=${CHAIN_ID} -remote=${DEV_REMOTE} \ + 2>&1 | $(run_logname) gnoweb # Tailwind CSS in development mode diff --git a/gno.land/pkg/gnoweb/alias.go b/gno.land/pkg/gnoweb/alias.go index 06bb3941e41..a837a2dcb49 100644 --- a/gno.land/pkg/gnoweb/alias.go +++ b/gno.land/pkg/gnoweb/alias.go @@ -39,10 +39,10 @@ func AliasAndRedirectMiddleware(next http.Handler, analytics bool) http.Handler // Check if the request path matches a redirect if newPath, ok := Redirects[r.URL.Path]; ok { http.Redirect(w, r, newPath, http.StatusFound) - components.RenderRedirectComponent(w, components.RedirectData{ + components.RedirectView(components.RedirectData{ To: newPath, WithAnalytics: analytics, - }) + }).Render(w) return } diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index 1deea86644b..61773ef39af 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -7,11 +7,6 @@ import ( "path" "strings" - markdown "github.com/yuin/goldmark-highlighting/v2" - - "github.com/alecthomas/chroma/v2" - chromahtml "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/alecthomas/chroma/v2/styles" "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/yuin/goldmark" @@ -32,10 +27,12 @@ type AppConfig struct { ChainID string // AssetsPath is the base path to the gnoweb assets. AssetsPath string - // AssetDir, if set, will be used for assets instead of the embedded public directory + // AssetDir, if set, will be used for assets instead of the embedded public directory. AssetsDir string // FaucetURL, if specified, will be the URL to which `/faucet` redirects. FaucetURL string + // Domain is the domain used by the node. + Domain string } // NewDefaultAppConfig returns a new default [AppConfig]. The default sets @@ -43,104 +40,83 @@ type AppConfig struct { // to be served on /public/. func NewDefaultAppConfig() *AppConfig { const defaultRemote = "127.0.0.1:26657" - return &AppConfig{ - // same as Remote by default NodeRemote: defaultRemote, RemoteHelp: defaultRemote, ChainID: "dev", AssetsPath: "/public/", + Domain: "gno.land", } } -var chromaStyle = mustGetStyle("friendly") - -func mustGetStyle(name string) *chroma.Style { - s := styles.Get(name) - if s == nil { - panic("unable to get chroma style") - } - return s -} - -// NewRouter initializes the gnoweb router, with the given logger and config. +// NewRouter initializes the gnoweb router with the specified logger and configuration. func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { - chromaOptions := []chromahtml.Option{ - chromahtml.WithLineNumbers(true), - chromahtml.WithLinkableLineNumbers(true, "L"), - chromahtml.WithClasses(true), - chromahtml.ClassPrefix("chroma-"), + // Initialize RPC Client + client, err := client.NewHTTPClient(cfg.NodeRemote) + if err != nil { + return nil, fmt.Errorf("unable to create HTTP client: %w", err) } - mdopts := []goldmark.Option{ - goldmark.WithExtensions( - markdown.NewHighlighting( - markdown.WithFormatOptions(chromaOptions...), - ), - ), - } + // Setup web client HTML + webcfg := NewDefaultHTMLWebClientConfig(client) + webcfg.Domain = cfg.Domain if cfg.UnsafeHTML { - mdopts = append(mdopts, goldmark.WithRendererOptions(mdhtml.WithXHTML(), mdhtml.WithUnsafe())) + webcfg.GoldmarkOptions = append(webcfg.GoldmarkOptions, goldmark.WithRendererOptions( + mdhtml.WithXHTML(), mdhtml.WithUnsafe(), + )) } + webcli := NewHTMLClient(logger, webcfg) - md := goldmark.New(mdopts...) + // Setup StaticMetadata + chromaStylePath := path.Join(cfg.AssetsPath, "_chroma", "style.css") + staticMeta := StaticMetadata{ + Domain: cfg.Domain, + AssetsPath: cfg.AssetsPath, + ChromaPath: chromaStylePath, + RemoteHelp: cfg.RemoteHelp, + ChainId: cfg.ChainID, + Analytics: cfg.Analytics, + } - client, err := client.NewHTTPClient(cfg.NodeRemote) + // Configure WebHandler + webConfig := WebHandlerConfig{WebClient: webcli, Meta: staticMeta} + webhandler, err := NewWebHandler(logger, webConfig) if err != nil { - return nil, fmt.Errorf("unable to create http client: %w", err) + return nil, fmt.Errorf("unable to create web handler: %w", err) } - webcli := NewWebClient(logger, client, md) - - formatter := chromahtml.New(chromaOptions...) - chromaStylePath := path.Join(cfg.AssetsPath, "_chroma", "style.css") - - var webConfig WebHandlerConfig - - webConfig.RenderClient = webcli - webConfig.Formatter = newFormatterWithStyle(formatter, chromaStyle) - - // Static meta - webConfig.Meta.AssetsPath = cfg.AssetsPath - webConfig.Meta.ChromaPath = chromaStylePath - webConfig.Meta.RemoteHelp = cfg.RemoteHelp - webConfig.Meta.ChainId = cfg.ChainID - webConfig.Meta.Analytics = cfg.Analytics - - // Setup main handler - webhandler := NewWebHandler(logger, webConfig) + // Setup HTTP muxer mux := http.NewServeMux() - // Setup Webahndler along Alias Middleware + // Handle web handler with alias middleware mux.Handle("/", AliasAndRedirectMiddleware(webhandler, cfg.Analytics)) // Register faucet URL to `/faucet` if specified if cfg.FaucetURL != "" { mux.Handle("/faucet", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, cfg.FaucetURL, http.StatusFound) - components.RenderRedirectComponent(w, components.RedirectData{ + components.RedirectView(components.RedirectData{ To: cfg.FaucetURL, WithAnalytics: cfg.Analytics, - }) + }).Render(w) })) } - // setup assets + // Handle Chroma CSS requests + // XXX: probably move this elsewhere mux.Handle(chromaStylePath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Setup Formatter w.Header().Set("Content-Type", "text/css") - if err := formatter.WriteCSS(w, chromaStyle); err != nil { - logger.Error("unable to write css", "err", err) + if err := webcli.WriteFormatterCSS(w); err != nil { + logger.Error("unable to write CSS", "err", err) http.NotFound(w, r) } })) - // Normalize assets path - assetsBase := "/" + strings.Trim(cfg.AssetsPath, "/") + "/" - // Handle assets path + // XXX: add caching + assetsBase := "/" + strings.Trim(cfg.AssetsPath, "/") + "/" if cfg.AssetsDir != "" { - logger.Debug("using assets dir instead of embed assets", "dir", cfg.AssetsDir) + logger.Debug("using assets dir instead of embedded assets", "dir", cfg.AssetsDir) mux.Handle(assetsBase, DevAssetHandler(assetsBase, cfg.AssetsDir)) } else { mux.Handle(assetsBase, AssetHandler()) diff --git a/gno.land/pkg/gnoweb/app_test.go b/gno.land/pkg/gnoweb/app_test.go index 4fac6e0b971..699570220e3 100644 --- a/gno.land/pkg/gnoweb/app_test.go +++ b/gno.land/pkg/gnoweb/app_test.go @@ -24,18 +24,18 @@ func TestRoutes(t *testing.T) { status int substring string }{ - {"/", ok, "Welcome"}, // assert / gives 200 (OK). assert / contains "Welcome". + {"/", ok, "Welcome"}, // Check if / returns 200 (OK) and contains "Welcome". {"/about", ok, "blockchain"}, - {"/r/gnoland/blog", ok, ""}, // whatever content + {"/r/gnoland/blog", ok, ""}, // Any content {"/r/gnoland/blog$help", ok, "AdminSetAdminAddr"}, {"/r/gnoland/blog/", ok, "admin.gno"}, {"/r/gnoland/blog/admin.gno", ok, ">func<"}, {"/r/gnoland/blog$help&func=Render", ok, "Render(path)"}, {"/r/gnoland/blog$help&func=Render&path=foo/bar", ok, `value="foo/bar"`}, // {"/r/gnoland/blog$help&func=NonExisting", ok, "NonExisting not found"}, // XXX(TODO) - {"/r/demo/users:administrator", ok, "address"}, - {"/r/demo/users", ok, "moul"}, - {"/r/demo/users/users.gno", ok, "// State"}, + {"/r/gnoland/users/v1:archives", ok, "Address"}, + {"/r/gnoland/users/v1", ok, "registry"}, + {"/r/gnoland/users/v1/users.gno", ok, "reValidUsername"}, {"/r/demo/deep/very/deep", ok, "it works!"}, {"/r/demo/deep/very/deep?arg1=val1&arg2=val2", ok, "hi ?arg1=val1&arg2=val2"}, {"/r/demo/deep/very/deep:bob", ok, "hi bob"}, @@ -47,15 +47,25 @@ func TestRoutes(t *testing.T) { {"/game-of-realms", found, "/contribute"}, {"/gor", found, "/contribute"}, {"/blog", found, "/r/gnoland/blog"}, - {"/404/not/found/", notFound, ""}, + {"/r/docs/optional_render", http.StatusOK, "No Render"}, + {"/r/not/found/", notFound, ""}, + {"/404/not/found", notFound, ""}, {"/아스키문자가아닌경로", notFound, ""}, {"/%ED%85%8C%EC%8A%A4%ED%8A%B8", notFound, ""}, {"/グノー", notFound, ""}, - {"/⚛️", notFound, ""}, + {"/\u269B\uFE0F", notFound, ""}, // Unicode {"/p/demo/flow/LICENSE", ok, "BSD 3-Clause"}, + // Test assets + {"/public/styles.css", ok, ""}, + {"/public/js/index.js", ok, ""}, + {"/public/_chroma/style.css", ok, ""}, + {"/public/imgs/gnoland.svg", ok, ""}, + // Test Toc + {"/", ok, `href="#learn-about-gnoland"`}, } rootdir := gnoenv.RootDir() + println(rootdir) genesis := integration.LoadDefaultGenesisTXsFile(t, "tendermint_test", rootdir) config, _ := integration.TestingNodeConfig(t, rootdir, genesis...) node, remoteAddr := integration.TestingInMemoryNode(t, log.NewTestingLogger(t), config) @@ -66,8 +76,7 @@ func TestRoutes(t *testing.T) { logger := log.NewTestingLogger(t) - // set the `remoteAddr` of the client to the listening address of the - // node, which is randomly assigned. + // Initialize the router with the current node's remote address router, err := NewRouter(logger, cfg) require.NoError(t, err) @@ -85,24 +94,24 @@ func TestRoutes(t *testing.T) { func TestAnalytics(t *testing.T) { routes := []string{ - // special realms - "/", // home + // Special realms + "/", // Home "/about", "/start", - // redirects + // Redirects "/game-of-realms", "/getting-started", "/blog", "/boards", - // realm, source, help page + // Realm, source, help page "/r/gnoland/blog", "/r/gnoland/blog/admin.gno", - "/r/demo/users:administrator", + "/r/gnoland/users/v1", "/r/gnoland/blog$help", - // special pages + // Special pages "/404-not-found", } @@ -125,6 +134,7 @@ func TestAnalytics(t *testing.T) { request := httptest.NewRequest(http.MethodGet, route, nil) response := httptest.NewRecorder() + router.ServeHTTP(response, request) assert.Contains(t, response.Body.String(), "sa.gno.services") @@ -143,6 +153,7 @@ func TestAnalytics(t *testing.T) { request := httptest.NewRequest(http.MethodGet, route, nil) response := httptest.NewRecorder() + router.ServeHTTP(response, request) assert.NotContains(t, response.Body.String(), "sa.gno.services") diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml deleted file mode 100644 index 3824eb5894f..00000000000 --- a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml +++ /dev/null @@ -1,18 +0,0 @@ -{{ define "breadcrumb" }} -
    - {{- range $index, $part := .Parts }} - {{- if $index }} -
  1. - {{- else }} -
  2. - {{- end }} - {{ $part.Name }} -
  3. - {{- end }} - {{- if .Args }} -
  4. - {{ .Args }} -
  5. - {{- end }} -
-{{ end }} diff --git a/gno.land/pkg/gnoweb/components/component.go b/gno.land/pkg/gnoweb/components/component.go new file mode 100644 index 00000000000..7a7c8a3d160 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/component.go @@ -0,0 +1,35 @@ +package components + +import ( + "io" +) + +type Component interface { + Render(w io.Writer) error +} + +type TemplateComponent struct { + name string + data any +} + +func (c *TemplateComponent) Render(w io.Writer) error { + return tmpl.ExecuteTemplate(w, c.name, c.data) +} + +func NewTemplateComponent(name string, data any) Component { + return &TemplateComponent{name: name, data: data} +} + +type readerComponent struct { + io.Reader +} + +func NewReaderComponent(reader io.Reader) Component { + return &readerComponent{reader} +} + +func (c *readerComponent) Render(w io.Writer) (err error) { + _, err = io.Copy(w, c) + return err +} diff --git a/gno.land/pkg/gnoweb/components/directory.go b/gno.land/pkg/gnoweb/components/directory.go deleted file mode 100644 index 6e47db3b2c4..00000000000 --- a/gno.land/pkg/gnoweb/components/directory.go +++ /dev/null @@ -1,15 +0,0 @@ -package components - -import ( - "io" -) - -type DirData struct { - PkgPath string - Files []string - FileCounter int -} - -func RenderDirectoryComponent(w io.Writer, data DirData) error { - return tmpl.ExecuteTemplate(w, "renderDir", data) -} diff --git a/gno.land/pkg/gnoweb/components/help.go b/gno.land/pkg/gnoweb/components/help.go deleted file mode 100644 index e819705006b..00000000000 --- a/gno.land/pkg/gnoweb/components/help.go +++ /dev/null @@ -1,51 +0,0 @@ -package components - -import ( - "html/template" - "io" - "strings" - - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types -) - -type HelpData struct { - // Selected function - SelectedFunc string - SelectedArgs map[string]string - - RealmName string - Functions []vm.FunctionSignature - ChainId string - Remote string - PkgPath string -} - -func registerHelpFuncs(funcs template.FuncMap) { - funcs["helpFuncSignature"] = func(fsig vm.FunctionSignature) (string, error) { - var fsigStr strings.Builder - - fsigStr.WriteString(fsig.FuncName) - fsigStr.WriteRune('(') - for i, param := range fsig.Params { - if i > 0 { - fsigStr.WriteString(", ") - } - fsigStr.WriteString(param.Name) - } - fsigStr.WriteRune(')') - - return fsigStr.String(), nil - } - - funcs["getSelectedArgValue"] = func(data HelpData, param vm.NamedType) (string, error) { - if data.SelectedArgs == nil { - return "", nil - } - - return data.SelectedArgs[param.Name], nil - } -} - -func RenderHelpComponent(w io.Writer, data HelpData) error { - return tmpl.ExecuteTemplate(w, "renderHelp", data) -} diff --git a/gno.land/pkg/gnoweb/components/help.gohtml b/gno.land/pkg/gnoweb/components/help.gohtml deleted file mode 100644 index 535cb56e9d6..00000000000 --- a/gno.land/pkg/gnoweb/components/help.gohtml +++ /dev/null @@ -1,110 +0,0 @@ -{{ define "renderHelp" }} - {{ $data := . }} -
-
-
-
-

{{ .RealmName }}

-
-
-
- - - - -
-
- - -
-
-
- -
- - {{ range .Functions }} -
-

{{ .FuncName }}

-
-
-

Params

-
- {{ $funcName := .FuncName }} - {{ range .Params }} -
-
- - -
-
- {{ end }} -
-
-
-
-

Command

-
- -
gnokey maketx call -pkgpath "{{ $.PkgPath }}" -func "{{ .FuncName }}" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid "{{ $.ChainId }}"{{ range .Params }} -args ""{{ end }} -remote "{{ $.Remote }}" ADDRESS
-
-
-
- {{ end }} - -
-
-
-{{ end }} diff --git a/gno.land/pkg/gnoweb/components/index.go b/gno.land/pkg/gnoweb/components/index.go deleted file mode 100644 index 0cc020ae261..00000000000 --- a/gno.land/pkg/gnoweb/components/index.go +++ /dev/null @@ -1,47 +0,0 @@ -package components - -import ( - "context" - "html/template" - "io" - "net/url" -) - -type HeadData struct { - Title string - Description string - Canonical string - Image string - URL string - ChromaPath string - AssetsPath string - Analytics bool -} - -type HeaderData struct { - RealmPath string - Breadcrumb BreadcrumbData - WebQuery url.Values -} - -type FooterData struct { - Analytics bool - AssetsPath string -} - -type IndexData struct { - HeadData - HeaderData - FooterData - Body template.HTML -} - -func IndexComponent(data IndexData) Component { - return func(ctx context.Context, tmpl *template.Template, w io.Writer) error { - return tmpl.ExecuteTemplate(w, "index", data) - } -} - -func RenderIndexComponent(w io.Writer, data IndexData) error { - return tmpl.ExecuteTemplate(w, "index", data) -} diff --git a/gno.land/pkg/gnoweb/components/index.gohtml b/gno.land/pkg/gnoweb/components/index.gohtml deleted file mode 100644 index a87decc14bf..00000000000 --- a/gno.land/pkg/gnoweb/components/index.gohtml +++ /dev/null @@ -1,159 +0,0 @@ -{{ define "index" }} - - - {{ template "head" .HeadData }} - - {{ template "spritesvg" }} - - - {{ template "header" .HeaderData }} - - - {{ template "main" .Body }} - - - {{ template "footer" .FooterData }} - - -{{ end }} - -{{ define "head" }} - - - - {{ .Title }} - - - - - - - - - - - {{ if .Canonical }} - - {{ end }} - - - - - - - - - - - - - - - - - - - - - - - - - - -{{ end }} - -{{ define "header" }} -
- -
-{{ end }} - -{{ define "main" }} - {{ . }} -{{ end }} - -{{ define "footer" }} - - -{{- if .Analytics -}} {{- template "analytics" }} {{- end -}} - -{{- end }} - -{{- define "analytics" -}} - - - -{{- end -}} diff --git a/gno.land/pkg/gnoweb/components/layout_footer.go b/gno.land/pkg/gnoweb/components/layout_footer.go new file mode 100644 index 00000000000..381b0063bbc --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layout_footer.go @@ -0,0 +1,50 @@ +package components + +type FooterData struct { + Analytics bool + AssetsPath string + Sections []FooterSection +} + +type FooterLink struct { + Label string + URL string +} + +type FooterSection struct { + Title string + Links []FooterLink +} + +func EnrichFooterData(data FooterData) FooterData { + data.Sections = []FooterSection{ + { + Title: "Footer navigation", + Links: []FooterLink{ + {Label: "About", URL: "/about"}, + {Label: "Docs", URL: "https://docs.gno.land/"}, + {Label: "Faucet", URL: "https://faucet.gno.land/"}, + {Label: "Blog", URL: "https://gno.land/r/gnoland/blog"}, + {Label: "Status", URL: "https://status.gnoteam.com/"}, + }, + }, + { + Title: "Social media", + Links: []FooterLink{ + {Label: "GitHub", URL: "https://github.com/gnolang/gno"}, + {Label: "Twitter", URL: "https://twitter.com/_gnoland"}, + {Label: "Discord", URL: "https://discord.gg/S8nKUqwkPn"}, + {Label: "YouTube", URL: "https://www.youtube.com/@_gnoland"}, + }, + }, + { + Title: "Legal", + Links: []FooterLink{ + {Label: "Terms", URL: "https://github.com/gnolang/gno/blob/master/LICENSE.md"}, + {Label: "Privacy", URL: "https://github.com/gnolang/gno/blob/master/LICENSE.md"}, + }, + }, + } + + return data +} diff --git a/gno.land/pkg/gnoweb/components/layout_header.go b/gno.land/pkg/gnoweb/components/layout_header.go new file mode 100644 index 00000000000..2c0492bdd6e --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layout_header.go @@ -0,0 +1,88 @@ +package components + +import ( + "net/url" + + "github.com/gnolang/gno/gno.land/pkg/gnoweb/weburl" +) + +type HeaderLink struct { + Label string + URL string + Icon string + IsActive bool +} + +type HeaderData struct { + RealmPath string + RealmURL weburl.GnoURL + Breadcrumb BreadcrumbData + Links []HeaderLink + ChainId string + Remote string +} + +func StaticHeaderLinks(u weburl.GnoURL, handle string) []HeaderLink { + contentURL, sourceURL, helpURL := u, u, u + contentURL.WebQuery = url.Values{} + sourceURL.WebQuery = url.Values{"source": {""}} + helpURL.WebQuery = url.Values{"help": {""}} + + links := []HeaderLink{ + { + Label: "Content", + URL: contentURL.EncodeWebURL(), + Icon: "ico-content", + IsActive: isActive(u.WebQuery, "Content"), + }, + { + Label: "Source", + URL: sourceURL.EncodeWebURL(), + Icon: "ico-code", + IsActive: isActive(u.WebQuery, "Source"), + }, + } + + switch handle { + case "p": + // Will have docs soon + + default: + links = append(links, HeaderLink{ + Label: "Actions", + URL: helpURL.EncodeWebURL(), + Icon: "ico-helper", + IsActive: isActive(u.WebQuery, "Actions"), + }) + } + + return links +} + +func EnrichHeaderData(data HeaderData) HeaderData { + data.RealmPath = data.RealmURL.EncodeURL() + + var handle string + if len(data.Breadcrumb.Parts) > 0 { + handle = data.Breadcrumb.Parts[0].Name + } else { + handle = "" + } + + data.Links = StaticHeaderLinks(data.RealmURL, handle) + + return data +} + +func isActive(webQuery url.Values, label string) bool { + switch label { + case "Content": + return !(webQuery.Has("source") || webQuery.Has("help")) + case "Source": + return webQuery.Has("source") + case "Actions": + return webQuery.Has("help") + default: + return false + } +} diff --git a/gno.land/pkg/gnoweb/components/layout_index.go b/gno.land/pkg/gnoweb/components/layout_index.go new file mode 100644 index 00000000000..f56ddfe1514 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layout_index.go @@ -0,0 +1,69 @@ +package components + +// Layout +const ( + SidebarLayout = "sidebar" + FullLayout = "full" +) + +type HeadData struct { + Title string + Description string + Canonical string + Image string + URL string + ChromaPath string + AssetsPath string + Analytics bool + Remote string + ChainId string +} + +type IndexData struct { + HeadData + HeaderData + FooterData + BodyView *View +} + +type indexLayoutParams struct { + IndexData + + // Additional data + IsDevmodView bool + Layout string + ViewType string +} + +func IndexLayout(data IndexData) Component { + data.FooterData = EnrichFooterData(data.FooterData) + data.HeaderData = EnrichHeaderData(data.HeaderData) + + dataLayout := indexLayoutParams{ + IndexData: data, + // Set default value + Layout: FullLayout, + ViewType: data.BodyView.String(), + } + + switch data.BodyView.Type { + case RealmViewType: + dataLayout.Layout = SidebarLayout + + case HelpViewType: + dataLayout.IsDevmodView = true + dataLayout.Layout = SidebarLayout + + case SourceViewType: + dataLayout.IsDevmodView = true + dataLayout.Layout = SidebarLayout + + case DirectoryViewType: + dataLayout.IsDevmodView = true + + case StatusViewType: + dataLayout.IsDevmodView = true + } + + return NewTemplateComponent("index", dataLayout) +} diff --git a/gno.land/pkg/gnoweb/components/layouts/analytics.html b/gno.land/pkg/gnoweb/components/layouts/analytics.html new file mode 100644 index 00000000000..8782c80d7ae --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/analytics.html @@ -0,0 +1,7 @@ +{{- define "layout/analytics" -}} + + + +{{- end -}} diff --git a/gno.land/pkg/gnoweb/components/layouts/article.html b/gno.land/pkg/gnoweb/components/layouts/article.html new file mode 100644 index 00000000000..63862e9a4dc --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/article.html @@ -0,0 +1,3 @@ +{{ define "layout/article" }} +
{{ render .ComponentContent }}
+{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/aside.html b/gno.land/pkg/gnoweb/components/layouts/aside.html new file mode 100644 index 00000000000..2b33d7a8e2b --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/aside.html @@ -0,0 +1,10 @@ +{{- define "layout/aside" }} + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/footer.html b/gno.land/pkg/gnoweb/components/layouts/footer.html new file mode 100644 index 00000000000..1ae51add995 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/footer.html @@ -0,0 +1,23 @@ +{{ define "layouts/footer" }} + + + + + +{{- if .Analytics -}} {{- template "layout/analytics" }}{{- end -}} {{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/layouts/head.html b/gno.land/pkg/gnoweb/components/layouts/head.html new file mode 100644 index 00000000000..72f255ebdf6 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/head.html @@ -0,0 +1,48 @@ +{{ define "layouts/head" }} + + + + {{ .Title }} + + + + + + + + + + {{ if .Canonical }} + + {{ end }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/header.html b/gno.land/pkg/gnoweb/components/layouts/header.html new file mode 100644 index 00000000000..903a668120d --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/header.html @@ -0,0 +1,134 @@ +{{ define "layouts/header" }} +
+ +
+{{ end }} diff --git a/gno.land/pkg/gnoweb/components/layouts/index.html b/gno.land/pkg/gnoweb/components/layouts/index.html new file mode 100644 index 00000000000..c42a868e6fa --- /dev/null +++ b/gno.land/pkg/gnoweb/components/layouts/index.html @@ -0,0 +1,29 @@ +{{ define "index" -}} + + + + {{ template "layouts/head" .IndexData.HeadData -}} + + {{ template "ui/icons" -}} + + + {{ template "layouts/header" .IndexData.HeaderData -}} + + +
+
+ {{ render .IndexData.BodyView -}} +
+
+ + + {{ template "layouts/footer" .FooterData -}} + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/logosvg.gohtml b/gno.land/pkg/gnoweb/components/logosvg.gohtml deleted file mode 100644 index 5ebe6460ee3..00000000000 --- a/gno.land/pkg/gnoweb/components/logosvg.gohtml +++ /dev/null @@ -1,21 +0,0 @@ -{{ define "logosvg" }} - - - - - - - - - - - - - - - - - - - -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/realm.go b/gno.land/pkg/gnoweb/components/realm.go deleted file mode 100644 index 027760bb382..00000000000 --- a/gno.land/pkg/gnoweb/components/realm.go +++ /dev/null @@ -1,32 +0,0 @@ -package components - -import ( - "context" - "html/template" - "io" - - "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" -) - -type RealmTOCData struct { - Items []*markdown.TocItem -} - -func RealmTOCComponent(data *RealmTOCData) Component { - return func(ctx context.Context, tmpl *template.Template, w io.Writer) error { - return tmpl.ExecuteTemplate(w, "renderRealmToc", data) - } -} - -func RenderRealmTOCComponent(w io.Writer, data *RealmTOCData) error { - return tmpl.ExecuteTemplate(w, "renderRealmToc", data) -} - -type RealmData struct { - Content template.HTML - TocItems *RealmTOCData -} - -func RenderRealmComponent(w io.Writer, data RealmData) error { - return tmpl.ExecuteTemplate(w, "renderRealm", data) -} diff --git a/gno.land/pkg/gnoweb/components/realm.gohtml b/gno.land/pkg/gnoweb/components/realm.gohtml deleted file mode 100644 index 55f39ef36d7..00000000000 --- a/gno.land/pkg/gnoweb/components/realm.gohtml +++ /dev/null @@ -1,41 +0,0 @@ -{{ define "renderRealmToc" }} - -{{ end }} - -{{ define "renderRealm" }} -
-
- -
- - {{ .Content }} -
-
-
-{{ end }} diff --git a/gno.land/pkg/gnoweb/components/redirect.go b/gno.land/pkg/gnoweb/components/redirect.go deleted file mode 100644 index 873ddf56ff5..00000000000 --- a/gno.land/pkg/gnoweb/components/redirect.go +++ /dev/null @@ -1,12 +0,0 @@ -package components - -import "io" - -type RedirectData struct { - To string - WithAnalytics bool -} - -func RenderRedirectComponent(w io.Writer, data RedirectData) error { - return tmpl.ExecuteTemplate(w, "renderRedirect", data) -} diff --git a/gno.land/pkg/gnoweb/components/source.go b/gno.land/pkg/gnoweb/components/source.go deleted file mode 100644 index 23170776657..00000000000 --- a/gno.land/pkg/gnoweb/components/source.go +++ /dev/null @@ -1,20 +0,0 @@ -package components - -import ( - "html/template" - "io" -) - -type SourceData struct { - PkgPath string - Files []string - FileName string - FileSize string - FileLines int - FileCounter int - FileSource template.HTML -} - -func RenderSourceComponent(w io.Writer, data SourceData) error { - return tmpl.ExecuteTemplate(w, "renderSource", data) -} diff --git a/gno.land/pkg/gnoweb/components/source.gohtml b/gno.land/pkg/gnoweb/components/source.gohtml deleted file mode 100644 index cb2430b504a..00000000000 --- a/gno.land/pkg/gnoweb/components/source.gohtml +++ /dev/null @@ -1,57 +0,0 @@ -{{ define "renderSource" }} -
-
-
-
-

{{ .FileName }}

-
-
- {{ .FileSize }} · {{ .FileLines }} lines - -
-
- - -
-
- {{ .FileSource }} -
-
-
-
-{{ end }} diff --git a/gno.land/pkg/gnoweb/components/spritesvg.gohtml b/gno.land/pkg/gnoweb/components/spritesvg.gohtml deleted file mode 100644 index c061e97bf58..00000000000 --- a/gno.land/pkg/gnoweb/components/spritesvg.gohtml +++ /dev/null @@ -1,125 +0,0 @@ -{{ define "spritesvg" }} - - - Search - - - - - - - Apps - - - - Documentation - - - - Source - - - - Content - - - - File - - - - Folder - - - - - - - - - - - Download - - - - Copy - - - - - - - - - - -{{ end }} diff --git a/gno.land/pkg/gnoweb/components/status.gohtml b/gno.land/pkg/gnoweb/components/status.gohtml deleted file mode 100644 index 2321d1110bd..00000000000 --- a/gno.land/pkg/gnoweb/components/status.gohtml +++ /dev/null @@ -1,12 +0,0 @@ -{{ define "status" }} -
-
-
- gno land -

Error: {{ .Message }}

-

Something went wrong. Let’s find our way back!

- Go Back Home -
-
-
-{{ end }} diff --git a/gno.land/pkg/gnoweb/components/template.go b/gno.land/pkg/gnoweb/components/template.go index 9c08703f460..ee2605d1436 100644 --- a/gno.land/pkg/gnoweb/components/template.go +++ b/gno.land/pkg/gnoweb/components/template.go @@ -2,76 +2,57 @@ package components import ( "bytes" - "context" "embed" + "fmt" "html/template" - "io" "net/url" ) -//go:embed *.gohtml -var gohtml embed.FS +//go:embed ui/*.html views/*.html layouts/*.html +var html embed.FS -var funcMap = template.FuncMap{ +var funcMap = template.FuncMap{} + +var tmpl = template.New("web") + +func registerCommonFuncs(funcs template.FuncMap) { // NOTE: this method does NOT escape HTML, use with caution - "noescape_string": func(in string) template.HTML { + funcs["noescape_string"] = func(in string) template.HTML { return template.HTML(in) //nolint:gosec - }, + } // NOTE: this method does NOT escape HTML, use with caution - "noescape_bytes": func(in []byte) template.HTML { + funcs["noescape_bytes"] = func(in []byte) template.HTML { return template.HTML(in) //nolint:gosec - }, - "queryHas": func(vals url.Values, key string) bool { + } + // NOTE: this method does NOT escape HTML, use with caution + // Render Component element into raw html element + funcs["render"] = func(comp Component) (template.HTML, error) { + var buf bytes.Buffer + if err := comp.Render(&buf); err != nil { + return "", fmt.Errorf("unable to render component: %w", err) + } + + return template.HTML(buf.String()), nil //nolint:gosec + } + funcs["queryHas"] = func(vals url.Values, key string) bool { if vals == nil { return false } return vals.Has(key) - }, + } } -var tmpl = template.New("web").Funcs(funcMap) - func init() { + // Register templates functions + registerCommonFuncs(funcMap) registerHelpFuncs(funcMap) tmpl.Funcs(funcMap) + // Parse templates var err error - tmpl, err = tmpl.ParseFS(gohtml, "*.gohtml") + tmpl, err = tmpl.ParseFS(html, "layouts/*.html", "ui/*.html", "views/*.html") if err != nil { panic("unable to parse embed tempalates: " + err.Error()) } } - -type Component func(ctx context.Context, tmpl *template.Template, w io.Writer) error - -func (c Component) Render(ctx context.Context, w io.Writer) error { - return RenderComponent(ctx, w, c) -} - -func RenderComponent(ctx context.Context, w io.Writer, c Component) error { - var render *template.Template - funcmap := template.FuncMap{ - "render": func(cf Component) (string, error) { - var buf bytes.Buffer - if err := cf(ctx, render, &buf); err != nil { - return "", err - } - - return buf.String(), nil - }, - } - - render = tmpl.Funcs(funcmap) - return c(ctx, render, w) -} - -type StatusData struct { - Message string -} - -func RenderStatusComponent(w io.Writer, message string) error { - return tmpl.ExecuteTemplate(w, "status", StatusData{ - Message: message, - }) -} diff --git a/gno.land/pkg/gnoweb/components/ui/breadcrumb.html b/gno.land/pkg/gnoweb/components/ui/breadcrumb.html new file mode 100644 index 00000000000..7a295f024b3 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/breadcrumb.html @@ -0,0 +1,90 @@ +{{ define "ui/breadcrumb" }} +
+
    + + {{- range $index, $part := .Parts }} +
  1. + + {{ $part.Name }} + +
  2. + {{- end }} + + + + {{- if .ArgParts }} + {{- range $index, $arg := .ArgParts }} +
  3. + + {{ $arg.Name }} + +
  4. + {{- end }} + {{- end }} + + + + {{ $allQueries := .Queries }} + {{- range $index, $query := $allQueries }} +
  5. + +
  6. + {{- end }} +
+ +
+{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/btn_copy.html b/gno.land/pkg/gnoweb/components/ui/btn_copy.html new file mode 100644 index 00000000000..5f6e7c6f7a8 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/btn_copy.html @@ -0,0 +1,6 @@ +{{ define "ui/copy" }} + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/code_wrapper.html b/gno.land/pkg/gnoweb/components/ui/code_wrapper.html new file mode 100644 index 00000000000..b05adb0532d --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/code_wrapper.html @@ -0,0 +1,3 @@ +{{ define "ui/code_wrapper" }} +
{{ render . }}
+{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/ui/expend_label.html b/gno.land/pkg/gnoweb/components/ui/expend_label.html new file mode 100644 index 00000000000..53b92721c07 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/expend_label.html @@ -0,0 +1,14 @@ +{{ define "ui/expend_label" }} + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/header_link.html b/gno.land/pkg/gnoweb/components/ui/header_link.html new file mode 100644 index 00000000000..e70c6a7f733 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/header_link.html @@ -0,0 +1,14 @@ + +{{ define "ui/header_link" }} + +
+ + + + + +
+
+{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/ui/help_function.html b/gno.land/pkg/gnoweb/components/ui/help_function.html new file mode 100644 index 00000000000..2e00d48c69d --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/help_function.html @@ -0,0 +1,72 @@ + +{{ define "ui/help_function" }} + {{ $data := . }} + {{ range .Functions }} +
+

+ {{ .FuncName }} +

+
+
+

Params

+
+ {{ $funcName := .FuncName }} + {{ range .Params }} +
+
+ + +
+
+ {{ end }} +
+
+
+
+

Command

+
+ + {{/* prettier-ignore-start */}} +
gnokey query -remote "{{ $.Remote }}" auth/accounts/ADDRESS
+gnokey maketx call -pkgpath "{{ $.PkgPath }}" -func "{{ .FuncName }}" {{ range .Params }} -args ""{{ end }} -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" ADDRESS > call.tx
+gnokey sign -tx-path call.tx -chainid "{{ $.ChainId }}" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
+gnokey broadcast -remote "{{ $.Remote }}" call.tx
+ {{/* prettier-ignore-end */}} +
+
+
+ {{ end }} +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/icons.html b/gno.land/pkg/gnoweb/components/ui/icons.html new file mode 100644 index 00000000000..75700909aaf --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/icons.html @@ -0,0 +1,221 @@ +{{ define "ui/icons" }} + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/logo.html b/gno.land/pkg/gnoweb/components/ui/logo.html new file mode 100644 index 00000000000..a61beafd3cd --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/logo.html @@ -0,0 +1,51 @@ +{{ define "ui/logo" }} + + + + + + + + + + + + + + + + + + + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/toc_generic.html b/gno.land/pkg/gnoweb/components/ui/toc_generic.html new file mode 100644 index 00000000000..3cd027f2a0c --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/toc_generic.html @@ -0,0 +1,14 @@ +{{- define "ui/toc_generic" }} + +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/ui/toc_realm.html b/gno.land/pkg/gnoweb/components/ui/toc_realm.html new file mode 100644 index 00000000000..dc09db4d0e6 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/ui/toc_realm.html @@ -0,0 +1,10 @@ +{{ define "ui/toc_realm" }} + +{{ end }} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.go b/gno.land/pkg/gnoweb/components/ui_breadcrumb.go similarity index 65% rename from gno.land/pkg/gnoweb/components/breadcrumb.go rename to gno.land/pkg/gnoweb/components/ui_breadcrumb.go index 8eda02a9f4d..a2bee951f28 100644 --- a/gno.land/pkg/gnoweb/components/breadcrumb.go +++ b/gno.land/pkg/gnoweb/components/ui_breadcrumb.go @@ -9,9 +9,15 @@ type BreadcrumbPart struct { URL string } +type QueryParam struct { + Key string + Value string +} + type BreadcrumbData struct { - Parts []BreadcrumbPart - Args string + Parts []BreadcrumbPart + ArgParts []BreadcrumbPart + Queries []QueryParam } func RenderBreadcrumpComponent(w io.Writer, data BreadcrumbData) error { diff --git a/gno.land/pkg/gnoweb/components/view.go b/gno.land/pkg/gnoweb/components/view.go new file mode 100644 index 00000000000..f2da05b4b90 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view.go @@ -0,0 +1,32 @@ +package components + +import ( + "fmt" + "io" +) + +type ViewType string + +type View struct { + Type ViewType + Component +} + +func (v *View) String() string { + return string(v.Type) +} + +func (v *View) Render(w io.Writer) error { + if err := v.Component.Render(w); err != nil { + return fmt.Errorf("view %q error: %w", string(v.Type), err) + } + + return nil +} + +func NewTemplateView(typ ViewType, name string, data any) *View { + return &View{ + Type: typ, + Component: NewTemplateComponent(name, data), + } +} diff --git a/gno.land/pkg/gnoweb/components/view_directory.go b/gno.land/pkg/gnoweb/components/view_directory.go new file mode 100644 index 00000000000..a105291a4dd --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_directory.go @@ -0,0 +1,13 @@ +package components + +const DirectoryViewType ViewType = "dir-view" + +type DirData struct { + PkgPath string + Files []string + FileCounter int +} + +func DirectoryView(data DirData) *View { + return NewTemplateView(DirectoryViewType, "renderDir", data) +} diff --git a/gno.land/pkg/gnoweb/components/view_help.go b/gno.land/pkg/gnoweb/components/view_help.go new file mode 100644 index 00000000000..3473f5a3f42 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_help.go @@ -0,0 +1,83 @@ +package components + +import ( + "html/template" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types +) + +const HelpViewType ViewType = "help-view" + +type HelpData struct { + // Selected function + SelectedFunc string + SelectedArgs map[string]string + + RealmName string + Functions []vm.FunctionSignature + ChainId string + Remote string + PkgPath string +} + +type HelpTocData struct { + Icon string + Items []HelpTocItem +} + +type HelpTocItem struct { + Link string + Text string +} + +type helpViewParams struct { + HelpData + Article ArticleData + ComponentTOC Component +} + +func registerHelpFuncs(funcs template.FuncMap) { + funcs["getSelectedArgValue"] = func(data HelpData, param vm.NamedType) (string, error) { + if data.SelectedArgs == nil { + return "", nil + } + + return data.SelectedArgs[param.Name], nil + } +} + +func HelpView(data HelpData) *View { + tocData := HelpTocData{ + Icon: "code", + Items: make([]HelpTocItem, len(data.Functions)), + } + + for i, fn := range data.Functions { + sig := fn.FuncName + "(" + for j, param := range fn.Params { + if j > 0 { + sig += ", " + } + sig += param.Name + } + sig += ")" + + tocData.Items[i] = HelpTocItem{ + Link: "#func-" + fn.FuncName, + Text: sig, + } + } + + toc := NewTemplateComponent("ui/toc_generic", tocData) + content := NewTemplateComponent("ui/help_function", data) + viewData := helpViewParams{ + HelpData: data, + Article: ArticleData{ + ComponentContent: content, + Classes: "", + }, + ComponentTOC: toc, + } + + return NewTemplateView(HelpViewType, "renderHelp", viewData) +} diff --git a/gno.land/pkg/gnoweb/components/view_realm.go b/gno.land/pkg/gnoweb/components/view_realm.go new file mode 100644 index 00000000000..49372244fd4 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_realm.go @@ -0,0 +1,38 @@ +package components + +import ( + "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" +) + +const RealmViewType ViewType = "realm-view" + +type RealmTOCData struct { + Items []*markdown.TocItem +} + +type RealmData struct { + ComponentContent Component + TocItems *RealmTOCData +} + +type ArticleData struct { + ComponentContent Component + Classes string +} + +type realmViewParams struct { + Article ArticleData + ComponentTOC Component +} + +func RealmView(data RealmData) *View { + viewData := realmViewParams{ + Article: ArticleData{ + ComponentContent: data.ComponentContent, + Classes: "realm-view lg:row-start-1", + }, + ComponentTOC: NewTemplateComponent("ui/toc_realm", data.TocItems), + } + + return NewTemplateView(RealmViewType, "renderRealm", viewData) +} diff --git a/gno.land/pkg/gnoweb/components/view_redirect.go b/gno.land/pkg/gnoweb/components/view_redirect.go new file mode 100644 index 00000000000..57d2f59e20a --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_redirect.go @@ -0,0 +1,12 @@ +package components + +const RedirectViewType = "redirect-view" + +type RedirectData struct { + To string + WithAnalytics bool +} + +func RedirectView(data RedirectData) *View { + return NewTemplateView(RedirectViewType, "renderRedirect", data) +} diff --git a/gno.land/pkg/gnoweb/components/view_source.go b/gno.land/pkg/gnoweb/components/view_source.go new file mode 100644 index 00000000000..7eb5227dea1 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_source.go @@ -0,0 +1,66 @@ +package components + +const SourceViewType ViewType = "source-view" + +type SourceData struct { + PkgPath string + Files []string + FileName string + FileSize string + FileLines int + FileCounter int + FileSource Component +} + +type SourceTocData struct { + Icon string + Items []SourceTocItem +} + +type SourceTocItem struct { + Link string + Text string +} + +type sourceViewParams struct { + Article ArticleData + Files []string + FileName string + FileSize string + FileLines int + FileCounter int + PkgPath string + ComponentTOC Component +} + +func SourceView(data SourceData) *View { + tocData := SourceTocData{ + Icon: "file", + Items: make([]SourceTocItem, len(data.Files)), + } + + for i, file := range data.Files { + tocData.Items[i] = SourceTocItem{ + Link: data.PkgPath + "$source&file=" + file, + Text: file, + } + } + + toc := NewTemplateComponent("ui/toc_generic", tocData) + content := NewTemplateComponent("ui/code_wrapper", data.FileSource) + viewData := sourceViewParams{ + Article: ArticleData{ + ComponentContent: content, + Classes: "source-view col-span-1 lg:col-span-7 lg:row-start-2 pb-24 text-gray-900", + }, + ComponentTOC: toc, + Files: data.Files, + FileName: data.FileName, + FileSize: data.FileSize, + FileLines: data.FileLines, + FileCounter: data.FileCounter, + PkgPath: data.PkgPath, + } + + return NewTemplateView(SourceViewType, "renderSource", viewData) +} diff --git a/gno.land/pkg/gnoweb/components/view_status.go b/gno.land/pkg/gnoweb/components/view_status.go new file mode 100644 index 00000000000..56477a4db0a --- /dev/null +++ b/gno.land/pkg/gnoweb/components/view_status.go @@ -0,0 +1,39 @@ +package components + +const StatusViewType ViewType = "status-view" + +// StatusData holds the dynamic fields for the "status" template +type StatusData struct { + Title string + Body string + ButtonURL string + ButtonText string +} + +// StatusErrorComponent returns a view for error scenarios +func StatusErrorComponent(message string) *View { + return NewTemplateView( + StatusViewType, + "status", + StatusData{ + Title: "Error: " + message, + Body: "Something went wrong.", + ButtonURL: "/", + ButtonText: "Go Back Home", + }, + ) +} + +// StatusNoRenderComponent returns a view for non-error notifications +func StatusNoRenderComponent(pkgPath string) *View { + return NewTemplateView( + StatusViewType, + "status", + StatusData{ + Title: "No Render", + Body: "This realm does not implement a Render() function.", + ButtonURL: pkgPath + "$source", + ButtonText: "View Realm Source", + }, + ) +} diff --git a/gno.land/pkg/gnoweb/components/directory.gohtml b/gno.land/pkg/gnoweb/components/views/directory.html similarity index 85% rename from gno.land/pkg/gnoweb/components/directory.gohtml rename to gno.land/pkg/gnoweb/components/views/directory.html index 2254886f7af..9aedd658def 100644 --- a/gno.land/pkg/gnoweb/components/directory.gohtml +++ b/gno.land/pkg/gnoweb/components/views/directory.html @@ -1,7 +1,4 @@ {{ define "renderDir" }} -
-
- {{ $pkgpath := .PkgPath }}
@@ -31,8 +28,5 @@

{{ $pkgpath }}

-
- -
{{ end }} diff --git a/gno.land/pkg/gnoweb/components/views/help.html b/gno.land/pkg/gnoweb/components/views/help.html new file mode 100644 index 00000000000..8be669d85d8 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/help.html @@ -0,0 +1,55 @@ +{{ define "renderHelp" }} + {{ $data := . }} + +
+
+

{{ .RealmName }}

+
+
+
+ + + + +
+
+ + +
+
+
+ + + {{ with render .ComponentTOC }} + {{ template "layout/aside" . }} + {{ end }} + + + + {{ template "layout/article" .Article }} +{{ end }} diff --git a/gno.land/pkg/gnoweb/components/views/realm.html b/gno.land/pkg/gnoweb/components/views/realm.html new file mode 100644 index 00000000000..6e0a771e701 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/realm.html @@ -0,0 +1,8 @@ +{{ define "renderRealm" }} + +{{ with render .ComponentTOC }} +{{ template "layout/aside" .}} +{{ end }} + + +{{ template "layout/article" .Article}} {{ end }} diff --git a/gno.land/pkg/gnoweb/components/redirect.gohtml b/gno.land/pkg/gnoweb/components/views/redirect.html similarity index 79% rename from gno.land/pkg/gnoweb/components/redirect.gohtml rename to gno.land/pkg/gnoweb/components/views/redirect.html index 45dac0981cd..cd192b850a2 100644 --- a/gno.land/pkg/gnoweb/components/redirect.gohtml +++ b/gno.land/pkg/gnoweb/components/views/redirect.html @@ -10,7 +10,7 @@ {{.To}} - {{- if .WithAnalytics -}} {{- template "analytics" }} {{- end -}} + {{- if .WithAnalytics -}} {{- template "layout/analytics" }} {{- end -}} -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/gno.land/pkg/gnoweb/components/views/source.html b/gno.land/pkg/gnoweb/components/views/source.html new file mode 100644 index 00000000000..44d39ca94ca --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/source.html @@ -0,0 +1,26 @@ +{{ define "renderSource" }} + + +{{ with render .ComponentTOC }} +{{ template "layout/aside" . }} +{{ end }} + + +
+
+

{{ .FileName }}

+
+
+ {{ .FileSize }} · {{ .FileLines }} lines + +
+
+ + +{{ template "layout/article" .Article }} +{{ end }} + + diff --git a/gno.land/pkg/gnoweb/components/views/status.html b/gno.land/pkg/gnoweb/components/views/status.html new file mode 100644 index 00000000000..f4533275789 --- /dev/null +++ b/gno.land/pkg/gnoweb/components/views/status.html @@ -0,0 +1,12 @@ +{{ define "status" }} +
+ gno land +

+ {{ .Title }} +

+

{{ .Body }}

+ + {{ .ButtonText }} + +
+{{ end }} diff --git a/gno.land/pkg/gnoweb/formatter.go b/gno.land/pkg/gnoweb/formatter.go deleted file mode 100644 index e172afe9e21..00000000000 --- a/gno.land/pkg/gnoweb/formatter.go +++ /dev/null @@ -1,25 +0,0 @@ -package gnoweb - -import ( - "io" - - "github.com/alecthomas/chroma/v2" - "github.com/alecthomas/chroma/v2/formatters/html" -) - -type Formatter interface { - Format(w io.Writer, iterator chroma.Iterator) error -} - -type formatterWithStyle struct { - *html.Formatter - style *chroma.Style -} - -func newFormatterWithStyle(formater *html.Formatter, style *chroma.Style) Formatter { - return &formatterWithStyle{Formatter: formater, style: style} -} - -func (f *formatterWithStyle) Format(w io.Writer, iterator chroma.Iterator) error { - return f.Formatter.Format(w, f.style, iterator) -} diff --git a/gno.land/pkg/gnoweb/frontend/css/input.css b/gno.land/pkg/gnoweb/frontend/css/input.css index f86076dbe54..69b02615e1d 100644 --- a/gno.land/pkg/gnoweb/frontend/css/input.css +++ b/gno.land/pkg/gnoweb/frontend/css/input.css @@ -3,7 +3,9 @@ font-style: normal; font-weight: 900; font-display: swap; - src: url("./fonts/roboto/roboto-mono-normal.woff2") format("woff2"), url("./fonts/roboto/roboto-mono-normal.woff") format("woff"); + src: + url("./fonts/roboto/roboto-mono-normal.woff2") format("woff2"), + url("./fonts/roboto/roboto-mono-normal.woff") format("woff"); } @font-face { @@ -21,8 +23,16 @@ @layer base { html { @apply font-interVar text-gray-600 bg-light text-200; - font-feature-settings: "kern" on, "liga" on, "calt" on, "zero" on; - -webkit-font-feature-settings: "kern" on, "liga" on, "calt" on, "zero" on; + font-feature-settings: + "kern" on, + "liga" on, + "calt" on, + "zero" on; + -webkit-font-feature-settings: + "kern" on, + "liga" on, + "calt" on, + "zero" on; text-size-adjust: 100%; -moz-osx-font-smoothing: grayscale; font-smoothing: antialiased; @@ -39,209 +49,199 @@ @apply my-0; } - .realm-content { - @apply text-200 break-words pt-10; + .realm-view { + @apply text-200 break-words pt-6 lg:pt-10; } - .realm-content > *:first-child { + .realm-view > *:first-child { @apply !mt-0; } - .realm-content a { + .realm-view a { @apply text-green-600 font-medium hover:underline; } - .realm-content h1, - .realm-content h2, - .realm-content h3, - .realm-content h4 { + .realm-view h1, + .realm-view h2, + .realm-view h3, + .realm-view h4 { @apply text-gray-900 mt-12 leading-tight; } - .realm-content h2, - .realm-content h2 * { + .realm-view h2, + .realm-view h2 * { @apply font-bold; } - .realm-content h3, - .realm-content h3 *, - .realm-content h4, - .realm-content h4 * { + .realm-view h3, + .realm-view h3 *, + .realm-view h4, + .realm-view h4 * { @apply font-semibold; } - .realm-content h1 + h2, - .realm-content h2 + h3, - .realm-content h3 + h4 { + .realm-view h1 + h2, + .realm-view h2 + h3, + .realm-view h3 + h4 { @apply mt-4; } - .realm-content h1 { + .realm-view h1 { @apply text-800 font-bold; } - .realm-content h2 { + .realm-view h2 { @apply text-600; } - .realm-content h3 { + .realm-view h3 { @apply text-400 text-gray-600 mt-10; } - .realm-content h4 { + .realm-view h4 { @apply text-300 text-gray-600 font-medium my-6; } - .realm-content p { + .realm-view p { @apply my-5; } - .realm-content strong { + .realm-view strong { @apply font-bold text-gray-900; } - .realm-content strong * { + .realm-view strong * { @apply font-bold; } - .realm-content em { + .realm-view em { @apply italic-subtle; } - .realm-content blockquote { + .realm-view blockquote { @apply border-l-4 border-gray-300 pl-4 text-gray-600 italic-subtle my-4; } - .realm-content ul, - .realm-content ol { + .realm-view ul, + .realm-view ol { @apply pl-4 my-6; } - .realm-content ul li, - .realm-content ol li { + .realm-view ul li, + .realm-view ol li { @apply mb-2; } - .realm-content img { + .realm-view img { @apply max-w-full my-8; } - .realm-content figure { + .realm-view figure { @apply my-6 text-center; } - .realm-content figcaption { + .realm-view figcaption { @apply text-100 text-gray-600; } - .realm-content :not(pre) > code { + .realm-view :not(pre) > code { @apply bg-gray-100 px-1 py-0.5 rounded-sm text-[.96em] font-mono; } - .realm-content pre { + .realm-view pre { @apply bg-gray-50 p-4 rounded overflow-x-auto font-mono; } - .realm-content hr { + .realm-view hr { @apply border-t border-gray-100 my-10; } - .realm-content table { - @apply w-full border-collapse my-8; + .realm-view table { + @apply my-8 block w-full max-w-full overflow-x-auto border-collapse; } - .realm-content th, - .realm-content td { - @apply border border-gray-300 px-4 py-2; + .realm-view th, + .realm-view td { + @apply border px-4 py-2 break-words whitespace-normal; } - .realm-content th { + .realm-view th { @apply bg-gray-100 font-bold; } - .realm-content caption { + .realm-view caption { @apply mt-2 text-100 text-gray-600 text-left; } - .realm-content q { + .realm-view q { @apply quotes; } - .realm-content q::before { + .realm-view q::before { content: open-quote; } - .realm-content q::after { + .realm-view q::after { content: close-quote; } - .realm-content ul ul, - .realm-content ul ol, - .realm-content ol ul, - .realm-content ol ol { + .realm-view ul ul, + .realm-view ul ol, + .realm-view ol ul, + .realm-view ol ol { @apply mt-3 mb-2 pl-4; } - .realm-content ul { + .realm-view ul { @apply list-disc; } - .realm-content ol { + .realm-view ol { @apply list-decimal; } - .realm-content table th:first-child, - .realm-content td:first-child { - @apply pl-0; - } - - .realm-content table th:last-child, - .realm-content td:last-child { - @apply pr-0; - } - - .realm-content abbr[title] { + .realm-view abbr[title] { @apply border-b border-dotted cursor-help; } - .realm-content details { + .realm-view details { @apply my-5; } - .realm-content summary { + .realm-view summary { @apply font-bold cursor-pointer; } - .realm-content a code { + .realm-view a code { @apply text-inherit; } - .realm-content video { + .realm-view video { @apply max-w-full my-8; } - .realm-content math { + .realm-view math { @apply font-mono; } - .realm-content small { + .realm-view small { @apply text-100; } - .realm-content del { + .realm-view del { @apply line-through; } - .realm-content sub { + .realm-view sub { @apply text-50 align-sub; } - .realm-content sup { + .realm-view sup { @apply text-50 align-super; } - .realm-content input, - .realm-content button { + .realm-view input, + .realm-view button { @apply px-4 py-2 border border-gray-300; } @@ -279,15 +279,43 @@ @apply block; } - :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) + main .realm-content, - :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) .main-navigation { + :is( + .main-header:has(#sidemenu-source:checked), + .main-header:has(#sidemenu-docs:checked), + .main-header:has(#sidemenu-meta:checked) + ) + + main + .realm-view, + :is( + .main-header:has(#sidemenu-source:checked), + .main-header:has(#sidemenu-docs:checked), + .main-header:has(#sidemenu-meta:checked) + ) + .main-navigation { @apply md:col-span-6; } - :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) + main #sidebar, - :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) .sidemenu { + :is( + .main-header:has(#sidemenu-source:checked), + .main-header:has(#sidemenu-docs:checked), + .main-header:has(#sidemenu-meta:checked) + ) + + main + #sidebar, + :is( + .main-header:has(#sidemenu-source:checked), + .main-header:has(#sidemenu-docs:checked), + .main-header:has(#sidemenu-meta:checked) + ) + .sidemenu { @apply md:col-span-4; } - :is(.main-header:has(#sidemenu-source:checked), .main-header:has(#sidemenu-docs:checked), .main-header:has(#sidemenu-meta:checked)) + main #sidebar::before { + :is( + .main-header:has(#sidemenu-source:checked), + .main-header:has(#sidemenu-docs:checked), + .main-header:has(#sidemenu-meta:checked) + ) + + main + #sidebar::before { @apply absolute block content-[''] top-0 w-[50vw] h-full -left-7 bg-gray-100 z-min; } @@ -295,27 +323,48 @@ main :is(.source-code) > pre { @apply !bg-light overflow-scroll rounded py-4 md:py-8 px-1 md:px-3 font-mono text-100 md:text-200; } - main .realm-content > pre a { + main .realm-view > pre a { @apply hover:no-underline; } - main :is(.realm-content, .source-code) > pre .chroma-ln:target { + main :is(.realm-view, .source-code) > pre .chroma-ln:target { @apply !bg-transparent; } - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-ln:target), - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-lnlinks:hover), - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-ln:target) .chroma-cl, - main :is(.realm-content, .source-code) > pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl { + main :is(.realm-view, .source-code) > pre .chroma-line:has(.chroma-ln:target), + main + :is(.realm-view, .source-code) + > pre + .chroma-line:has(.chroma-lnlinks:hover), + main + :is(.realm-view, .source-code) + > pre + .chroma-line:has(.chroma-ln:target) + .chroma-cl, + main + :is(.realm-view, .source-code) + > pre + .chroma-line:has(.chroma-lnlinks:hover) + .chroma-cl { @apply !bg-gray-100 rounded; } - main :is(.realm-content, .source-code) > pre .chroma-ln { + main :is(.realm-view, .source-code) > pre .chroma-ln { @apply scroll-mt-24; } + + .dev-mode .toc-expend-btn { + @apply bg-gray-100 hover:bg-gray-50 cursor-pointer border lg:border-none lg:bg-transparent; + } + .dev-mode #sidebar-summary { + @apply bg-light lg:bg-transparent; + } + .dev-mode .toc-nav { + @apply font-mono; + } } @layer utilities { .italic-subtle { - font-style: oblique 10deg; + font-style: oblique 14deg; } .quotes { @@ -343,4 +392,15 @@ -ms-overflow-style: none; scrollbar-width: none; } + + .field-content { + field-sizing: content; + } +} + +/* supports rules */ +@supports not (field-sizing: content) { + .focus-no-field-sizing\:w-20:focus { + width: 5rem !important; + } } diff --git a/gno.land/pkg/gnoweb/frontend/css/tx.config.js b/gno.land/pkg/gnoweb/frontend/css/tx.config.js index 21b6a101dd6..06aa685676a 100644 --- a/gno.land/pkg/gnoweb/frontend/css/tx.config.js +++ b/gno.land/pkg/gnoweb/frontend/css/tx.config.js @@ -1,7 +1,7 @@ const pxToRem = (px) => px / 16; export default { - content: ["./components/**/*.{gohtml,ts}"], + content: ["./components/**/*.{html,ts}"], theme: { screens: { xs: `${pxToRem(360)}rem`, @@ -26,6 +26,7 @@ export default { borderRadius: { sm: `${pxToRem(4)}rem`, DEFAULT: `${pxToRem(6)}rem`, + full: "9999px", }, colors: { light: "#FFFFFF", @@ -68,5 +69,6 @@ export default { 900: `${pxToRem(42)}rem`, }, }, + safelist: ["realm-view", { pattern: /^realm-view/ }], plugins: [], }; diff --git a/gno.land/pkg/gnoweb/frontend/js/copy.ts b/gno.land/pkg/gnoweb/frontend/js/copy.ts index f3e5c725783..5f559e7eb46 100644 --- a/gno.land/pkg/gnoweb/frontend/js/copy.ts +++ b/gno.land/pkg/gnoweb/frontend/js/copy.ts @@ -9,7 +9,7 @@ class Copy { private isAnimationRunning: boolean = false; private static SELECTORS = { - button: "[data-copy-btn]", + button: ".js-copy-btn", icon: `[data-copy-icon] > use`, content: (id: string) => `[data-copy-content="${id}"]`, }; diff --git a/gno.land/pkg/gnoweb/frontend/js/index.ts b/gno.land/pkg/gnoweb/frontend/js/index.ts index 3927f794b94..47b60f70360 100644 --- a/gno.land/pkg/gnoweb/frontend/js/index.ts +++ b/gno.land/pkg/gnoweb/frontend/js/index.ts @@ -6,15 +6,15 @@ const modules: Record = { copy: { - selector: "[data-copy-btn]", + selector: ".js-copy-btn", path: "/public/js/copy.js", }, help: { - selector: "#help", + selector: ".js-help-view", path: "/public/js/realmhelp.js", }, searchBar: { - selector: "#header-searchbar", + selector: ".js-header-searchbar", path: "/public/js/searchbar.js", }, }; diff --git a/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts b/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts index d72102e2a2e..950c85cdbe3 100644 --- a/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts +++ b/gno.land/pkg/gnoweb/frontend/js/realmhelp.ts @@ -1,4 +1,4 @@ -import { debounce } from "./utils"; +import { debounce, escapeShellSpecialChars } from "./utils"; class Help { private DOM: { @@ -11,7 +11,7 @@ class Help { private funcList: HelpFunc[]; private static SELECTORS = { - container: "#help", + container: ".js-help-view", func: "[data-func]", addressInput: "[data-role='help-input-addr']", cmdModeSelect: "[data-role='help-select-mode']", @@ -67,7 +67,7 @@ class Help { localStorage.setItem("helpAddressInput", address); this.funcList.forEach((func) => func.updateAddr(address)); - }); + }, 50); addressInput?.addEventListener("input", () => debouncedUpdate(addressInput)); cmdModeSelect?.addEventListener("change", (e) => { @@ -124,7 +124,7 @@ class HelpFunc { private bindEvents(): void { const debouncedUpdate = debounce((paramName: string, paramValue: string) => { if (paramName) this.updateArg(paramName, paramValue); - }); + }, 50); this.DOM.el.addEventListener("input", (e) => { const target = e.target as HTMLInputElement; @@ -143,10 +143,11 @@ class HelpFunc { } public updateArg(paramName: string, paramValue: string): void { + const escapedValue = escapeShellSpecialChars(paramValue); this.DOM.args .filter((arg) => arg.dataset.arg === paramName) .forEach((arg) => { - arg.textContent = paramValue || ""; + arg.textContent = escapedValue || ""; }); } diff --git a/gno.land/pkg/gnoweb/frontend/js/searchbar.ts b/gno.land/pkg/gnoweb/frontend/js/searchbar.ts index 6cca444aa0f..a6859245af6 100644 --- a/gno.land/pkg/gnoweb/frontend/js/searchbar.ts +++ b/gno.land/pkg/gnoweb/frontend/js/searchbar.ts @@ -8,7 +8,7 @@ class SearchBar { private baseUrl: string; private static SELECTORS = { - container: "#header-searchbar", + container: ".js-header-searchbar", inputSearch: "[data-role='header-input-search']", breadcrumb: "[data-role='header-breadcrumb-search']", }; diff --git a/gno.land/pkg/gnoweb/frontend/js/utils.ts b/gno.land/pkg/gnoweb/frontend/js/utils.ts index 83de509efa5..d975b4516f3 100644 --- a/gno.land/pkg/gnoweb/frontend/js/utils.ts +++ b/gno.land/pkg/gnoweb/frontend/js/utils.ts @@ -10,3 +10,7 @@ export function debounce void>(func: T, delay: num }, delay); }; } + +export function escapeShellSpecialChars(arg: string): string { + return arg.replace(/([$`"\\!|&;<>*?{}()])/g, "\\$1"); +} diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index 20f19e4405a..01327b3f124 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -4,24 +4,21 @@ import ( "bytes" "errors" "fmt" - "html/template" - "io" "log/slog" "net/http" - "path/filepath" + "path" "slices" "strings" "time" - "github.com/alecthomas/chroma/v2" - "github.com/alecthomas/chroma/v2/lexers" "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types + "github.com/gnolang/gno/gno.land/pkg/gnoweb/weburl" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // For error types ) -const DefaultChainDomain = "gno.land" - +// StaticMetadata holds static configuration for a web handler. type StaticMetadata struct { + Domain string AssetsPath string ChromaPath string RemoteHelp string @@ -29,166 +26,196 @@ type StaticMetadata struct { Analytics bool } +// WebHandlerConfig configures a WebHandler. type WebHandlerConfig struct { - Meta StaticMetadata - RenderClient *WebClient - Formatter Formatter + Meta StaticMetadata + WebClient WebClient } +// validate checks if the WebHandlerConfig is valid. +func (cfg WebHandlerConfig) validate() error { + if cfg.WebClient == nil { + return errors.New("no `WebClient` configured") + } + return nil +} + +// WebHandler processes HTTP requests. type WebHandler struct { - formatter Formatter + Logger *slog.Logger + Static StaticMetadata + Client WebClient +} - logger *slog.Logger - static StaticMetadata - webcli *WebClient +// PageData groups layout, component, and dev mode information. +type PageData struct { + Layout string + Component string + IsDevmodView bool } -func NewWebHandler(logger *slog.Logger, cfg WebHandlerConfig) *WebHandler { - if cfg.RenderClient == nil { - logger.Error("no renderer has been defined") +// NewWebHandler creates a new WebHandler. +func NewWebHandler(logger *slog.Logger, cfg WebHandlerConfig) (*WebHandler, error) { + if err := cfg.validate(); err != nil { + return nil, fmt.Errorf("config validate error: %w", err) } return &WebHandler{ - formatter: cfg.Formatter, - webcli: cfg.RenderClient, - logger: logger, - static: cfg.Meta, - } + Client: cfg.WebClient, + Static: cfg.Meta, + Logger: logger, + }, nil } +// ServeHTTP handles HTTP requests. func (h *WebHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - h.logger.Debug("receiving request", "method", r.Method, "path", r.URL.Path) + h.Logger.Debug("receiving request", "method", r.Method, "path", r.URL.Path) if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } + w.Header().Add("Content-Type", "text/html; charset=utf-8") h.Get(w, r) } +// Get processes a GET HTTP request. func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { - var body bytes.Buffer - start := time.Now() defer func() { - h.logger.Debug("request completed", + h.Logger.Debug("request completed", "url", r.URL.String(), "elapsed", time.Since(start).String()) }() - var indexData components.IndexData - indexData.HeadData.AssetsPath = h.static.AssetsPath - indexData.HeadData.ChromaPath = h.static.ChromaPath - indexData.FooterData.Analytics = h.static.Analytics - indexData.FooterData.AssetsPath = h.static.AssetsPath + indexData := components.IndexData{ + HeadData: components.HeadData{ + AssetsPath: h.Static.AssetsPath, + ChromaPath: h.Static.ChromaPath, + ChainId: h.Static.ChainId, + Remote: h.Static.RemoteHelp, + }, + FooterData: components.FooterData{ + Analytics: h.Static.Analytics, + AssetsPath: h.Static.AssetsPath, + }, + } - // Render the page body into the buffer var status int - gnourl, err := ParseGnoURL(r.URL) - if err != nil { - h.logger.Warn("page not found", "path", r.URL.Path, "err", err) - status, err = http.StatusNotFound, components.RenderStatusComponent(&body, "page not found") - } else { - // TODO: real data (title & description) - indexData.HeadData.Title = "gno.land - " + gnourl.Path - - // Header - indexData.HeaderData.RealmPath = gnourl.Encode(EncodePath | EncodeArgs | EncodeQuery | EncodeNoEscape) - indexData.HeaderData.Breadcrumb = generateBreadcrumbPaths(gnourl) - indexData.HeaderData.WebQuery = gnourl.WebQuery - - // Render - switch { - case gnourl.IsRealm(), gnourl.IsPure(): - status, err = h.renderPackage(&body, gnourl) - default: - h.logger.Debug("invalid path: path is neither a pure package or a realm") - status, err = http.StatusNotFound, components.RenderStatusComponent(&body, "page not found") - } + status, indexData.BodyView = h.prepareIndexBodyView(r, &indexData) + + // Render the final page with the rendered body + w.WriteHeader(status) + if err := components.IndexLayout(indexData).Render(w); err != nil { + h.Logger.Error("failed to render index component", "error", err) } +} +// prepareIndexBodyView prepares the data and main view for the index. +func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components.IndexData) (int, *components.View) { + gnourl, err := weburl.ParseGnoURL(r.URL) if err != nil { - http.Error(w, "internal server error", http.StatusInternalServerError) - return + h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "error", err) + return http.StatusNotFound, components.StatusErrorComponent("invalid path") } - w.WriteHeader(status) - - // NOTE: HTML escaping should have already been done by markdown rendering package - indexData.Body = template.HTML(body.String()) //nolint:gosec - - // Render the final page with the rendered body - if err = components.RenderIndexComponent(w, indexData); err != nil { - h.logger.Error("failed to render index component", "err", err) + breadcrumb := generateBreadcrumbPaths(gnourl) + indexData.HeadData.Title = h.Static.Domain + " - " + gnourl.Path + indexData.HeaderData = components.HeaderData{ + Breadcrumb: breadcrumb, + RealmURL: *gnourl, + ChainId: h.Static.ChainId, + Remote: h.Static.RemoteHelp, } - return + switch { + case gnourl.IsRealm(), gnourl.IsPure(): + return h.GetPackageView(gnourl, indexData) + default: + h.Logger.Debug("invalid path: path is neither a pure package or a realm") + return http.StatusBadRequest, components.StatusErrorComponent("invalid path") + } } -func (h *WebHandler) renderPackage(w io.Writer, gnourl *GnoURL) (status int, err error) { - h.logger.Info("component render", "path", gnourl.Path, "args", gnourl.Args) - - // Display realm help page? +// GetPackageView handles package pages. +func (h *WebHandler) GetPackageView(gnourl *weburl.GnoURL, indexData *components.IndexData) (int, *components.View) { + // Handle Help page if gnourl.WebQuery.Has("help") { - return h.renderRealmHelp(w, gnourl) + return h.GetHelpView(gnourl, indexData) } - // Display package source page? - switch { - case gnourl.WebQuery.Has("source"): - return h.renderRealmSource(w, gnourl) - case gnourl.IsFile(): - // Fill webquery with file infos - return h.renderRealmSource(w, gnourl) - case gnourl.IsDir(), gnourl.IsPure(): - return h.renderRealmDirectory(w, gnourl) + // Handle Source page + if gnourl.WebQuery.Has("source") || gnourl.IsFile() { + return h.GetSourceView(gnourl, indexData) } - // Render content into the content buffer + // Handle Source page + if gnourl.IsDir() || gnourl.IsPure() { + return h.GetDirectoryView(gnourl, indexData) + } + + // Ultimately get realm view + return h.GetRealmView(gnourl, indexData) +} + +func (h *WebHandler) GetRealmView(gnourl *weburl.GnoURL, indexData *components.IndexData) (int, *components.View) { var content bytes.Buffer - meta, err := h.webcli.Render(&content, gnourl.Path, gnourl.EncodeArgs()) + + meta, err := h.Client.RenderRealm(&content, gnourl.Path, gnourl.EncodeArgs()) if err != nil { - if errors.Is(err, vm.InvalidPkgPathError{}) { - return http.StatusNotFound, components.RenderStatusComponent(w, "not found") + if errors.Is(err, ErrRenderNotDeclared) { + return http.StatusOK, components.StatusNoRenderComponent(gnourl.Path) } - h.logger.Error("unable to render markdown", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to render realm", "error", err, "path", gnourl.EncodeURL()) + return GetClientErrorStatusPage(gnourl, err) + } + + // HTML Head metadata + if meta.Head.Title != "" { + indexData.Title = meta.Head.Title + " - " + h.Static.Domain + } else { + indexData.HeadData.Title = h.Static.Domain + " - " + gnourl.Path + " Realm" } - err = components.RenderRealmComponent(w, components.RealmData{ + if meta.Head.Description != "" { + indexData.Description = meta.Head.Description + } else { + indexData.Description = "Explore the " + gnourl.Path + " realm on " + h.Static.Domain + "." + } + + return http.StatusOK, components.RealmView(components.RealmData{ TocItems: &components.RealmTOCData{ - Items: meta.Items, + Items: meta.Toc.Items, }, - // NOTE: `content` should have already been escaped by - Content: template.HTML(content.String()), //nolint:gosec - }) - if err != nil { - h.logger.Error("unable to render template", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") - } - // Write the rendered content to the response writer - return http.StatusOK, nil + // NOTE: `RenderRealm` should ensure that HTML content is + // sanitized before rendering + ComponentContent: components.NewReaderComponent(&content), + }) } -func (h *WebHandler) renderRealmHelp(w io.Writer, gnourl *GnoURL) (status int, err error) { - fsigs, err := h.webcli.Functions(gnourl.Path) +func (h *WebHandler) GetHelpView(gnourl *weburl.GnoURL, indexData *components.IndexData) (int, *components.View) { + fsigs, err := h.Client.Functions(gnourl.Path) if err != nil { - h.logger.Error("unable to fetch path functions", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to fetch path functions", "error", err) + return GetClientErrorStatusPage(gnourl, err) } - var selArgs map[string]string - var selFn string - if selFn = gnourl.WebQuery.Get("func"); selFn != "" { + // HTML Head metadata + indexData.HeadData.Title = h.Static.Domain + " - " + gnourl.Path + " reference and interactions" + indexData.Description = "Explore " + gnourl.Path + " realm functions, and learn how to interact with them on " + h.Static.Domain + "." + + // Get selected function + selArgs := make(map[string]string) + selFn := gnourl.WebQuery.Get("func") + if selFn != "" { for _, fn := range fsigs { if selFn != fn.FuncName { continue } - selArgs = make(map[string]string) for _, param := range fn.Params { selArgs[param.Name] = gnourl.WebQuery.Get(param.Name) } @@ -198,148 +225,108 @@ func (h *WebHandler) renderRealmHelp(w io.Writer, gnourl *GnoURL) (status int, e } } - // Catch last name of the path - // XXX: we should probably add a helper within the template - realmName := filepath.Base(gnourl.Path) - err = components.RenderHelpComponent(w, components.HelpData{ + realmName := path.Base(gnourl.Path) + return http.StatusOK, components.HelpView(components.HelpData{ SelectedFunc: selFn, SelectedArgs: selArgs, RealmName: realmName, - ChainId: h.static.ChainId, // TODO: get chain domain and use that. - PkgPath: filepath.Join(DefaultChainDomain, gnourl.Path), - Remote: h.static.RemoteHelp, + ChainId: h.Static.ChainId, + PkgPath: path.Join(h.Static.Domain, gnourl.Path), + Remote: h.Static.RemoteHelp, Functions: fsigs, }) - if err != nil { - h.logger.Error("unable to render helper", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") - } - - return http.StatusOK, nil } -func (h *WebHandler) renderRealmSource(w io.Writer, gnourl *GnoURL) (status int, err error) { +func (h *WebHandler) GetSourceView(gnourl *weburl.GnoURL, indexData *components.IndexData) (int, *components.View) { pkgPath := gnourl.Path - - files, err := h.webcli.Sources(pkgPath) + files, err := h.Client.Sources(pkgPath) if err != nil { - h.logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to list sources file", "path", gnourl.Path, "error", err) + return GetClientErrorStatusPage(gnourl, err) } if len(files) == 0 { - h.logger.Debug("no files available", "path", gnourl.Path) - return http.StatusOK, components.RenderStatusComponent(w, "no files available") - } - - file := gnourl.WebQuery.Get("file") // webquery override file - if file == "" { - file = gnourl.File + h.Logger.Debug("no files available", "path", gnourl.Path) + return http.StatusOK, components.StatusErrorComponent("no files available") } var fileName string - if file == "" { - fileName = files[0] // Default to the first file if none specified - } else if slices.Contains(files, file) { - fileName = file // Use specified file if it exists - } else { - h.logger.Error("unable to render source", "file", file, "err", "file does not exist") - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + if gnourl.IsFile() { // check path file from path first + fileName = gnourl.File + } else if file := gnourl.WebQuery.Get("file"); file != "" { + fileName = file } - source, err := h.webcli.SourceFile(pkgPath, fileName) - if err != nil { - h.logger.Error("unable to get source file", "file", fileName, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + if fileName == "" { + fileName = files[0] // fallback on the first file } - // XXX: we should either do this on the front or in the markdown parsing side - fileLines := strings.Count(string(source), "\n") - fileSizeKb := float64(len(source)) / 1024.0 - fileSizeStr := fmt.Sprintf("%.2f Kb", fileSizeKb) - - // Highlight code source - hsource, err := h.highlightSource(fileName, source) + var source bytes.Buffer + meta, err := h.Client.SourceFile(&source, pkgPath, fileName) if err != nil { - h.logger.Error("unable to highlight source file", "file", fileName, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to get source file", "file", fileName, "error", err) + return GetClientErrorStatusPage(gnourl, err) } - err = components.RenderSourceComponent(w, components.SourceData{ + // HTML Head metadata + indexData.HeadData.Title = h.Static.Domain + " - " + fileName + " source code in " + gnourl.Path + indexData.Description = "Explore the " + fileName + " source code in the " + gnourl.Path + " file on " + h.Static.Domain + "." + + fileSizeStr := fmt.Sprintf("%.2f Kb", meta.SizeKb) + return http.StatusOK, components.SourceView(components.SourceData{ PkgPath: gnourl.Path, Files: files, FileName: fileName, FileCounter: len(files), - FileLines: fileLines, + FileLines: meta.Lines, FileSize: fileSizeStr, - FileSource: template.HTML(hsource), //nolint:gosec + FileSource: components.NewReaderComponent(&source), }) - if err != nil { - h.logger.Error("unable to render helper", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") - } - - return http.StatusOK, nil } -func (h *WebHandler) renderRealmDirectory(w io.Writer, gnourl *GnoURL) (status int, err error) { - pkgPath := gnourl.Path - - files, err := h.webcli.Sources(pkgPath) +func (h *WebHandler) GetDirectoryView(gnourl *weburl.GnoURL, indexData *components.IndexData) (int, *components.View) { + pkgPath := strings.TrimSuffix(gnourl.Path, "/") + files, err := h.Client.Sources(pkgPath) if err != nil { - h.logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to list sources file", "path", gnourl.Path, "error", err) + return GetClientErrorStatusPage(gnourl, err) } if len(files) == 0 { - h.logger.Debug("no files available", "path", gnourl.Path) - return http.StatusOK, components.RenderStatusComponent(w, "no files available") + h.Logger.Debug("no files available", "path", gnourl.Path) + return http.StatusOK, components.StatusErrorComponent("no files available") } - err = components.RenderDirectoryComponent(w, components.DirData{ + // HTML Head metadata + indexData.HeadData.Title = h.Static.Domain + " - Browse " + gnourl.Path + " directory" + indexData.Description = "Explore the directory structure, files, and subdirectories of " + gnourl.Path + " on " + h.Static.Domain + ", and discover its source code and documentation." + + return http.StatusOK, components.DirectoryView(components.DirData{ PkgPath: gnourl.Path, Files: files, FileCounter: len(files), }) - if err != nil { - h.logger.Error("unable to render directory", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") - } - - return http.StatusOK, nil } -func (h *WebHandler) highlightSource(fileName string, src []byte) ([]byte, error) { - var lexer chroma.Lexer - - switch strings.ToLower(filepath.Ext(fileName)) { - case ".gno": - lexer = lexers.Get("go") - case ".md": - lexer = lexers.Get("markdown") - default: - lexer = lexers.Get("txt") // file kind not supported, fallback on `.txt` - } - - if lexer == nil { - return nil, fmt.Errorf("unsupported lexer for file %q", fileName) - } - - iterator, err := lexer.Tokenise(nil, string(src)) - if err != nil { - h.logger.Error("unable to ", "fileName", fileName, "err", err) +func GetClientErrorStatusPage(_ *weburl.GnoURL, err error) (int, *components.View) { + if err == nil { + return http.StatusOK, nil } - var buff bytes.Buffer - if err := h.formatter.Format(&buff, iterator); err != nil { - return nil, fmt.Errorf("unable to format source file %q: %w", fileName, err) + switch { + case errors.Is(err, ErrClientPathNotFound): + return http.StatusNotFound, components.StatusErrorComponent(err.Error()) + case errors.Is(err, ErrClientBadRequest): + return http.StatusInternalServerError, components.StatusErrorComponent("bad request") + case errors.Is(err, ErrClientResponse): + fallthrough // XXX: for now fallback as internal error + default: + return http.StatusInternalServerError, components.StatusErrorComponent("internal error") } - - return buff.Bytes(), nil } -func generateBreadcrumbPaths(url *GnoURL) components.BreadcrumbData { +func generateBreadcrumbPaths(url *weburl.GnoURL) components.BreadcrumbData { split := strings.Split(url.Path, "/") var data components.BreadcrumbData @@ -355,8 +342,29 @@ func generateBreadcrumbPaths(url *GnoURL) components.BreadcrumbData { }) } - if args := url.EncodeArgs(); args != "" { - data.Args = args + // Add args + if url.Args != "" { + argSplit := strings.Split(url.Args, "/") + nonEmptyArgs := slices.DeleteFunc(argSplit, func(a string) bool { + return a == "" + }) + + for i := range nonEmptyArgs { + data.ArgParts = append(data.ArgParts, components.BreadcrumbPart{ + Name: nonEmptyArgs[i], + URL: url.Path + ":" + strings.Join(nonEmptyArgs[:i+1], "/"), + }) + } + } + + // Add query params + for key, values := range url.Query { + for _, v := range values { + data.Queries = append(data.Queries, components.QueryParam{ + Key: key, + Value: v, + }) + } } return data diff --git a/gno.land/pkg/gnoweb/handler_test.go b/gno.land/pkg/gnoweb/handler_test.go new file mode 100644 index 00000000000..8321ad24be2 --- /dev/null +++ b/gno.land/pkg/gnoweb/handler_test.go @@ -0,0 +1,153 @@ +package gnoweb_test + +import ( + "log/slog" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoweb" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testingLogger struct { + *testing.T +} + +func (t *testingLogger) Write(b []byte) (n int, err error) { + t.T.Log(strings.TrimSpace(string(b))) + return len(b), nil +} + +// TestWebHandler_Get tests the Get method of WebHandler using table-driven tests. +func TestWebHandler_Get(t *testing.T) { + t.Parallel() + // Set up a mock package with some files and functions + mockPackage := &gnoweb.MockPackage{ + Domain: "example.com", + Path: "/r/mock/path", + Files: map[string]string{ + "render.gno": `package main; func Render(path string) string { return "one more time" }`, + "gno.mod": `module example.com/r/mock/path`, + "LicEnse": `my super license`, + }, + Functions: []vm.FunctionSignature{ + {FuncName: "SuperRenderFunction", Params: []vm.NamedType{ + {Name: "my_super_arg", Type: "string"}, + }}, + { + FuncName: "Render", Params: []vm.NamedType{{Name: "path", Type: "string"}}, + Results: []vm.NamedType{{Name: "", Type: "string"}}, + }, + }, + } + + // Create a mock web client with the mock package + webclient := gnoweb.NewMockWebClient(mockPackage) + + // Create a WebHandlerConfig with the mock web client and static metadata + config := gnoweb.WebHandlerConfig{ + WebClient: webclient, + } + + // Define test cases + cases := []struct { + Path string + Status int + Contain string // optional + Contains []string // optional + }{ + // Found + {Path: "/r/mock/path", Status: http.StatusOK, Contain: "[example.com]/r/mock/path"}, + + // Source page + {Path: "/r/mock/path/", Status: http.StatusOK, Contain: "Directory"}, + {Path: "/r/mock/path/render.gno", Status: http.StatusOK, Contain: "one more time"}, + {Path: "/r/mock/path/LicEnse", Status: http.StatusOK, Contain: "my super license"}, + {Path: "/r/mock/path$source&file=render.gno", Status: http.StatusOK, Contain: "one more time"}, + {Path: "/r/mock/path$source", Status: http.StatusOK, Contain: "module"}, // `gno.mod` by default + {Path: "/r/mock/path/license", Status: http.StatusNotFound}, + + // Help page + {Path: "/r/mock/path$help", Status: http.StatusOK, Contains: []string{ + "my_super_arg", + "SuperRenderFunction", + }}, + + // Package not found + {Path: "/r/invalid/path", Status: http.StatusNotFound, Contain: "not found"}, + + // Invalid path + {Path: "/r", Status: http.StatusBadRequest, Contain: "invalid path"}, + {Path: "/r/~!1337", Status: http.StatusNotFound, Contain: "invalid path"}, + } + + for _, tc := range cases { + t.Run(strings.TrimPrefix(tc.Path, "/"), func(t *testing.T) { + t.Parallel() + t.Logf("input: %+v", tc) + + // Initialize testing logger + logger := slog.New(slog.NewTextHandler(&testingLogger{t}, &slog.HandlerOptions{})) + + // Create a new WebHandler + handler, err := gnoweb.NewWebHandler(logger, config) + require.NoError(t, err) + + // Create a new HTTP request for each test case + req, err := http.NewRequest(http.MethodGet, tc.Path, nil) + require.NoError(t, err) + + // Create a ResponseRecorder to capture the response + rr := httptest.NewRecorder() + + // Invoke serve method + handler.ServeHTTP(rr, req) + + // Assert result + assert.Equal(t, tc.Status, rr.Code) + assert.Containsf(t, rr.Body.String(), tc.Contain, "rendered body should contain: %q", tc.Contain) + for _, contain := range tc.Contains { + assert.Containsf(t, rr.Body.String(), contain, "rendered body should contain: %q", contain) + } + }) + } +} + +// TestWebHandler_NoRender checks if gnoweb displays the `No Render` page properly. +// This happens when the render being queried does not have a Render function declared. +func TestWebHandler_NoRender(t *testing.T) { + t.Parallel() + + mockPath := "/r/mock/path" + mockPackage := &gnoweb.MockPackage{ + Domain: "gno.land", + Path: "/r/mock/path", + Files: map[string]string{ + "render.gno": `package main; func init() {}`, + "gno.mod": `module gno.land/r/mock/path`, + }, + } + + webclient := gnoweb.NewMockWebClient(mockPackage) + config := gnoweb.WebHandlerConfig{ + WebClient: webclient, + } + + logger := slog.New(slog.NewTextHandler(&testingLogger{t}, &slog.HandlerOptions{})) + handler, err := gnoweb.NewWebHandler(logger, config) + require.NoError(t, err, "failed to create WebHandler") + + req, err := http.NewRequest(http.MethodGet, mockPath, nil) + require.NoError(t, err, "failed to create HTTP request") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code, "unexpected status code") + expectedBody := "This realm does not implement a Render() function." + assert.Contains(t, rr.Body.String(), expectedBody, "rendered body should contain: %q", expectedBody) +} diff --git a/gno.land/pkg/gnoweb/markdown/meta.go b/gno.land/pkg/gnoweb/markdown/meta.go new file mode 100644 index 00000000000..1185d5206e6 --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/meta.go @@ -0,0 +1,277 @@ +// Updated meta Package from goldmark (http://github.com/yuin/goldmark). +// +// This extension parses YAML metadata blocks and stores metadata in a +// parser.Context. The updated version uses gopkg.in/yaml.v3 instead of +// gopkg.in/yaml.v2 and remove useless options such as table rendering. +package markdown + +import ( + "bytes" + "fmt" + + "github.com/yuin/goldmark" + gast "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/util" + + "gopkg.in/yaml.v3" +) + +type data struct { + Map map[string]interface{} + Items []yaml.Node + Error error + Node gast.Node +} + +var contextKey = parser.NewContextKey() + +// Option interface sets options for this extension. +type Option interface { + metaOption() +} + +// Get returns the YAML metadata. +func Get(pc parser.Context) map[string]interface{} { + v := pc.Get(contextKey) + if v == nil { + return nil + } + d := v.(*data) + return d.Map +} + +// TryGet tries to get YAML metadata. +// If there are YAML parsing errors, then nil and error are returned. +func TryGet(pc parser.Context) (map[string]interface{}, error) { + dtmp := pc.Get(contextKey) + if dtmp == nil { + return nil, nil + } + d := dtmp.(*data) + if d.Error != nil { + return nil, d.Error + } + return d.Map, nil +} + +// GetItems returns the YAML metadata nodes preserving defined key order. +func GetItems(pc parser.Context) []yaml.Node { + v := pc.Get(contextKey) + if v == nil { + return nil + } + d := v.(*data) + return d.Items +} + +// TryGetItems returns the YAML metadata nodes preserving defined key order. +// If there are YAML parsing errors, then nil and error are returned. +func TryGetItems(pc parser.Context) ([]yaml.Node, error) { + dtmp := pc.Get(contextKey) + if dtmp == nil { + return nil, nil + } + d := dtmp.(*data) + if d.Error != nil { + return nil, d.Error + } + return d.Items, nil +} + +type metaParser struct{} + +var defaultParser = &metaParser{} + +// NewParser returns a BlockParser that can parse YAML metadata blocks. +func NewParser() parser.BlockParser { + return defaultParser +} + +func isSeparator(line []byte) bool { + line = util.TrimRightSpace(util.TrimLeftSpace(line)) + for i := 0; i < len(line); i++ { + if line[i] != '-' { + return false + } + } + return true +} + +func (b *metaParser) Trigger() []byte { + return []byte{'-'} +} + +func (b *metaParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) { + linenum, _ := reader.Position() + if linenum != 0 { + return nil, parser.NoChildren + } + line, _ := reader.PeekLine() + if isSeparator(line) { + return gast.NewTextBlock(), parser.NoChildren + } + return nil, parser.NoChildren +} + +func (b *metaParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State { + line, segment := reader.PeekLine() + if isSeparator(line) && !util.IsBlank(line) { + reader.Advance(segment.Len()) + return parser.Close + } + node.Lines().Append(segment) + return parser.Continue | parser.NoChildren +} + +func (b *metaParser) Close(node gast.Node, reader text.Reader, pc parser.Context) { + lines := node.Lines() + var buf bytes.Buffer + for i := 0; i < lines.Len(); i++ { + segment := lines.At(i) + buf.Write(segment.Value(reader.Source())) + } + d := &data{} + d.Node = node + + // Unmarshal map to get d.Map. + metaMap := map[string]interface{}{} + if err := yaml.Unmarshal(buf.Bytes(), &metaMap); err != nil { + d.Error = err + } else { + d.Map = metaMap + } + + // Unmarshal yaml.Node to get full structure. + var metaRoot yaml.Node + if err := yaml.Unmarshal(buf.Bytes(), &metaRoot); err != nil { + d.Error = err + } else { + if metaRoot.Kind == yaml.DocumentNode && len(metaRoot.Content) > 0 { + if metaRoot.Content[0].Kind == yaml.MappingNode { + for _, item := range metaRoot.Content[0].Content { + d.Items = append(d.Items, *item) + } + } else { + d.Error = fmt.Errorf("YAML n'est pas une map") + } + } else { + d.Error = fmt.Errorf("structure YAML inattendue") + } + } + + pc.Set(contextKey, d) + + if d.Error == nil { + node.Parent().RemoveChild(node.Parent(), node) + } +} + +func (b *metaParser) CanInterruptParagraph() bool { + return false +} + +func (b *metaParser) CanAcceptIndentedLine() bool { + return false +} + +type astTransformer struct { + transformerConfig +} + +type transformerConfig struct { + // Stores metadata in ast.Document.Meta(). + StoresInDocument bool +} + +type transformerOption interface { + Option + // SetMetaOption sets options for the metadata parser. + SetMetaOption(*transformerConfig) +} + +var _ transformerOption = &withStoresInDocument{} + +type withStoresInDocument struct { + value bool +} + +func (o *withStoresInDocument) metaOption() {} + +func (o *withStoresInDocument) SetMetaOption(c *transformerConfig) { + c.StoresInDocument = o.value +} + +// WithStoresInDocument is a functional option that stores YAML metadata in ast.Document.Meta(). +func WithStoresInDocument() Option { + return &withStoresInDocument{ + value: true, + } +} + +func newTransformer(opts ...transformerOption) parser.ASTTransformer { + p := &astTransformer{ + transformerConfig: transformerConfig{ + StoresInDocument: false, + }, + } + for _, o := range opts { + o.SetMetaOption(&p.transformerConfig) + } + return p +} + +func (a *astTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) { + dtmp := pc.Get(contextKey) + if dtmp == nil { + return + } + d := dtmp.(*data) + if d.Error != nil { + msg := gast.NewString([]byte(fmt.Sprintf("", d.Error))) + msg.SetCode(true) + d.Node.AppendChild(d.Node, msg) + return + } + + if a.StoresInDocument { + for k, v := range d.Map { + node.AddMeta(k, v) + } + } +} + +type meta struct { + options []Option +} + +// Meta is an extension for goldmark. +var Meta = &meta{} + +// NewMetadata returns a new Meta extension. +func NewMetadata(opts ...Option) goldmark.Extender { + return &meta{ + options: opts, + } +} + +// Extend implements goldmark.Extender. +func (e *meta) Extend(m goldmark.Markdown) { + topts := []transformerOption{} + for _, opt := range e.options { + if topt, ok := opt.(transformerOption); ok { + topts = append(topts, topt) + } + } + m.Parser().AddOptions( + parser.WithBlockParsers( + util.Prioritized(NewParser(), 0), + ), + ) + m.Parser().AddOptions( + parser.WithASTTransformers( + util.Prioritized(newTransformer(topts...), 0), + ), + ) +} diff --git a/gno.land/pkg/gnoweb/markdown/meta_test.go b/gno.land/pkg/gnoweb/markdown/meta_test.go new file mode 100644 index 00000000000..f97795f5f4c --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/meta_test.go @@ -0,0 +1,147 @@ +package markdown + +import ( + "bytes" + "testing" + + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" +) + +func TestMeta(t *testing.T) { + markdown := goldmark.New( + goldmark.WithExtensions( + Meta, + ), + ) + source := `--- +Title: goldmark-meta +Summary: Add YAML metadata to the document +Tags: + - markdown + - goldmark +--- + +# Hello goldmark-meta +` + + var buf bytes.Buffer + context := parser.NewContext() + if err := markdown.Convert([]byte(source), &buf, parser.WithContext(context)); err != nil { + panic(err) + } + metaData := Get(context) + title := metaData["Title"] + s, ok := title.(string) + if !ok { + t.Error("Title not found in meta data or is not a string") + } + if s != "goldmark-meta" { + t.Errorf("Title must be %s, but got %v", "goldmark-meta", s) + } + if buf.String() != "

Hello goldmark-meta

\n" { + t.Errorf("should render '

Hello goldmark-meta

', but '%s'", buf.String()) + } + tags, ok := metaData["Tags"].([]interface{}) + if !ok { + t.Error("Tags not found in meta data or is not a slice") + } + if len(tags) != 2 { + t.Error("Tags must be a slice that has 2 elements") + } + if tags[0] != "markdown" { + t.Errorf("Tag#1 must be 'markdown', but got %s", tags[0]) + } + if tags[1] != "goldmark" { + t.Errorf("Tag#2 must be 'goldmark', but got %s", tags[1]) + } +} + +func TestMetaError(t *testing.T) { + markdown := goldmark.New( + goldmark.WithExtensions( + Meta, + ), + ) + source := `--- +Title: goldmark-meta +Summary: Add YAML metadata to the document +Tags: + - : { + } + - markdown + - goldmark +--- + +# Hello goldmark-meta +` + + var buf bytes.Buffer + context := parser.NewContext() + if err := markdown.Convert([]byte(source), &buf, parser.WithContext(context)); err != nil { + panic(err) + } + if buf.String() != `Title: goldmark-meta +Summary: Add YAML metadata to the document +Tags: + - : { + } + - markdown + - goldmark + +

Hello goldmark-meta

+` { + t.Error("invalid error output") + } + + v, err := TryGet(context) + if err == nil { + t.Error("error should not be nil") + } + if v != nil { + t.Error("data should be nil when there are errors") + } +} + +func TestMetaStoreInDocument(t *testing.T) { + markdown := goldmark.New( + goldmark.WithExtensions( + NewMetadata( + WithStoresInDocument(), + ), + ), + ) + source := `--- +Title: goldmark-meta +Summary: Add YAML metadata to the document +Tags: + - markdown + - goldmark +--- +` + + document := markdown.Parser().Parse(text.NewReader([]byte(source))) + metaData := document.OwnerDocument().Meta() + title := metaData["Title"] + s, ok := title.(string) + if !ok { + t.Error("Title not found in meta data or is not a string") + } + if s != "goldmark-meta" { + t.Errorf("Title must be %s, but got %v", "goldmark-meta", s) + } + tags, ok := metaData["Tags"].([]interface{}) + if !ok { + t.Error("Tags not found in meta data or is not a slice") + } + if len(tags) != 2 { + t.Error("Tags must be a slice that has 2 elements") + } + if tags[0] != "markdown" { + t.Errorf("Tag#1 must be 'markdown', but got %s", tags[0]) + } + if tags[1] != "goldmark" { + t.Errorf("Tag#2 must be 'goldmark', but got %s", tags[1]) + } +} diff --git a/gno.land/pkg/gnoweb/markdown/toc.go b/gno.land/pkg/gnoweb/markdown/toc.go index 59d4941fabf..ceafbd7cc96 100644 --- a/gno.land/pkg/gnoweb/markdown/toc.go +++ b/gno.land/pkg/gnoweb/markdown/toc.go @@ -45,7 +45,7 @@ type TocOptions struct { MinDepth, MaxDepth int } -func TocInspect(n ast.Node, src []byte, opts TocOptions) (*Toc, error) { +func TocInspect(n ast.Node, src []byte, opts TocOptions) (Toc, error) { // Appends an empty subitem to the given node // and returns a reference to it. appendChild := func(n *TocItem) *TocItem { @@ -114,7 +114,7 @@ func TocInspect(n ast.Node, src []byte, opts TocOptions) (*Toc, error) { root.Items = compactItems(root.Items) - return &Toc{Items: root.Items}, err + return Toc{Items: root.Items}, err } // compactItems removes items with no titles diff --git a/gno.land/pkg/gnoweb/public/js/copy.js b/gno.land/pkg/gnoweb/public/js/copy.js index 918a30b1ca3..2bcfea74f84 100644 --- a/gno.land/pkg/gnoweb/public/js/copy.js +++ b/gno.land/pkg/gnoweb/public/js/copy.js @@ -1 +1 @@ -var s=class o{DOM;static FEEDBACK_DELAY=750;btnClicked=null;btnClickedIcons=[];isAnimationRunning=!1;static SELECTORS={button:"[data-copy-btn]",icon:"[data-copy-icon] > use",content:t=>`[data-copy-content="${t}"]`};constructor(){this.DOM={el:document.querySelector("main")},this.DOM.el?this.init():console.warn("Copy: Main container not found.")}init(){this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("click",this.handleClick.bind(this))}handleClick(t){let e=t.target.closest(o.SELECTORS.button);if(!e)return;this.btnClicked=e,this.btnClickedIcons=Array.from(e.querySelectorAll(o.SELECTORS.icon));let i=e.getAttribute("data-copy-btn");if(!i){console.warn("Copy: No content ID found on the button.");return}let r=this.DOM.el?.querySelector(o.SELECTORS.content(i));r?this.copyToClipboard(r,this.btnClickedIcons):console.warn(`Copy: No content found for ID "${i}".`)}sanitizeContent(t){let n=t.innerHTML.replace(/]*class="chroma-ln"[^>]*>[\s\S]*?<\/span>/g,""),e=document.createElement("div");return e.innerHTML=n,e.textContent?.trim()||""}toggleIcons(t){t.forEach(n=>{n.classList.toggle("hidden")})}showFeedback(t){!this.btnClicked||this.isAnimationRunning===!0||(this.isAnimationRunning=!0,this.toggleIcons(t),window.setTimeout(()=>{this.toggleIcons(t),this.isAnimationRunning=!1},o.FEEDBACK_DELAY))}async copyToClipboard(t,n){let e=this.sanitizeContent(t);if(!navigator.clipboard){console.error("Copy: Clipboard API is not supported in this browser."),this.showFeedback(n);return}try{await navigator.clipboard.writeText(e),this.showFeedback(n)}catch(i){console.error("Copy: Error while copying text.",i),this.showFeedback(n)}}},a=()=>new s;export{a as default}; +var s=class o{DOM;static FEEDBACK_DELAY=750;btnClicked=null;btnClickedIcons=[];isAnimationRunning=!1;static SELECTORS={button:".js-copy-btn",icon:"[data-copy-icon] > use",content:t=>`[data-copy-content="${t}"]`};constructor(){this.DOM={el:document.querySelector("main")},this.DOM.el?this.init():console.warn("Copy: Main container not found.")}init(){this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("click",this.handleClick.bind(this))}handleClick(t){let e=t.target.closest(o.SELECTORS.button);if(!e)return;this.btnClicked=e,this.btnClickedIcons=Array.from(e.querySelectorAll(o.SELECTORS.icon));let i=e.getAttribute("data-copy-btn");if(!i){console.warn("Copy: No content ID found on the button.");return}let r=this.DOM.el?.querySelector(o.SELECTORS.content(i));r?this.copyToClipboard(r,this.btnClickedIcons):console.warn(`Copy: No content found for ID "${i}".`)}sanitizeContent(t){let n=t.innerHTML.replace(/]*class="chroma-ln"[^>]*>[\s\S]*?<\/span>/g,""),e=document.createElement("div");return e.innerHTML=n,e.textContent?.trim()||""}toggleIcons(t){t.forEach(n=>{n.classList.toggle("hidden")})}showFeedback(t){!this.btnClicked||this.isAnimationRunning===!0||(this.isAnimationRunning=!0,this.toggleIcons(t),window.setTimeout(()=>{this.toggleIcons(t),this.isAnimationRunning=!1},o.FEEDBACK_DELAY))}async copyToClipboard(t,n){let e=this.sanitizeContent(t);if(!navigator.clipboard){console.error("Copy: Clipboard API is not supported in this browser."),this.showFeedback(n);return}try{await navigator.clipboard.writeText(e),this.showFeedback(n)}catch(i){console.error("Copy: Error while copying text.",i),this.showFeedback(n)}}},a=()=>new s;export{a as default}; diff --git a/gno.land/pkg/gnoweb/public/js/index.js b/gno.land/pkg/gnoweb/public/js/index.js index e990dd91f5f..0230bbd64bc 100644 --- a/gno.land/pkg/gnoweb/public/js/index.js +++ b/gno.land/pkg/gnoweb/public/js/index.js @@ -1 +1 @@ -(()=>{let s={copy:{selector:"[data-copy-btn]",path:"/public/js/copy.js"},help:{selector:"#help",path:"/public/js/realmhelp.js"},searchBar:{selector:"#header-searchbar",path:"/public/js/searchbar.js"}},r=async({selector:e,path:o})=>{if(document.querySelector(e))try{(await import(o)).default()}catch(t){console.error(`Error while loading script ${o}:`,t)}else console.warn(`Module not loaded: no element matches selector "${e}"`)},l=async()=>{let e=Object.values(s).map(o=>r(o));await Promise.all(e)};document.addEventListener("DOMContentLoaded",l)})(); +(()=>{let t={copy:{selector:".js-copy-btn",path:"/public/js/copy.js"},help:{selector:".js-help-view",path:"/public/js/realmhelp.js"},searchBar:{selector:".js-header-searchbar",path:"/public/js/searchbar.js"}},r=async({selector:e,path:o})=>{if(document.querySelector(e))try{(await import(o)).default()}catch(s){console.error(`Error while loading script ${o}:`,s)}else console.warn(`Module not loaded: no element matches selector "${e}"`)},l=async()=>{let e=Object.values(t).map(o=>r(o));await Promise.all(e)};document.addEventListener("DOMContentLoaded",l)})(); diff --git a/gno.land/pkg/gnoweb/public/js/realmhelp.js b/gno.land/pkg/gnoweb/public/js/realmhelp.js index 5d4a3feeba6..7008a54514e 100644 --- a/gno.land/pkg/gnoweb/public/js/realmhelp.js +++ b/gno.land/pkg/gnoweb/public/js/realmhelp.js @@ -1 +1 @@ -function d(s,e=250){let t;return function(...a){t!==void 0&&clearTimeout(t),t=setTimeout(()=>{s.apply(this,a)},e)}}var l=class s{DOM;funcList;static SELECTORS={container:"#help",func:"[data-func]",addressInput:"[data-role='help-input-addr']",cmdModeSelect:"[data-role='help-select-mode']"};constructor(){this.DOM={el:document.querySelector(s.SELECTORS.container),funcs:[],addressInput:null,cmdModeSelect:null},this.funcList=[],this.DOM.el?this.init():console.warn("Help: Main container not found.")}init(){let{el:e}=this.DOM;e&&(this.DOM.funcs=Array.from(e.querySelectorAll(s.SELECTORS.func)),this.DOM.addressInput=e.querySelector(s.SELECTORS.addressInput),this.DOM.cmdModeSelect=e.querySelector(s.SELECTORS.cmdModeSelect),this.funcList=this.DOM.funcs.map(t=>new o(t)),this.restoreAddress(),this.bindEvents())}restoreAddress(){let{addressInput:e}=this.DOM;if(e){let t=localStorage.getItem("helpAddressInput");t&&(e.value=t,this.funcList.forEach(a=>a.updateAddr(t)))}}bindEvents(){let{addressInput:e,cmdModeSelect:t}=this.DOM,a=d(r=>{let n=r.value;localStorage.setItem("helpAddressInput",n),this.funcList.forEach(i=>i.updateAddr(n))});e?.addEventListener("input",()=>a(e)),t?.addEventListener("change",r=>{let n=r.target;this.funcList.forEach(i=>i.updateMode(n.value))})}},o=class s{DOM;funcName;static SELECTORS={address:"[data-role='help-code-address']",args:"[data-role='help-code-args']",mode:"[data-code-mode]",paramInput:"[data-role='help-param-input']"};constructor(e){this.DOM={el:e,addrs:Array.from(e.querySelectorAll(s.SELECTORS.address)),args:Array.from(e.querySelectorAll(s.SELECTORS.args)),modes:Array.from(e.querySelectorAll(s.SELECTORS.mode)),paramInputs:Array.from(e.querySelectorAll(s.SELECTORS.paramInput))},this.funcName=e.dataset.func||null,this.initializeArgs(),this.bindEvents()}static sanitizeArgsInput(e){let t=e.dataset.param||"",a=e.value.trim();return t||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:t,paramValue:a}}bindEvents(){let e=d((t,a)=>{t&&this.updateArg(t,a)});this.DOM.el.addEventListener("input",t=>{let a=t.target;if(a.dataset.role==="help-param-input"){let{paramName:r,paramValue:n}=s.sanitizeArgsInput(a);e(r,n)}})}initializeArgs(){this.DOM.paramInputs.forEach(e=>{let{paramName:t,paramValue:a}=s.sanitizeArgsInput(e);t&&this.updateArg(t,a)})}updateArg(e,t){this.DOM.args.filter(a=>a.dataset.arg===e).forEach(a=>{a.textContent=t||""})}updateAddr(e){this.DOM.addrs.forEach(t=>{t.textContent=e.trim()||"ADDRESS"})}updateMode(e){this.DOM.modes.forEach(t=>{let a=t.dataset.codeMode===e;t.classList.toggle("inline",a),t.classList.toggle("hidden",!a),t.dataset.copyContent=a?`help-cmd-${this.funcName}`:""})}},p=()=>new l;export{p as default}; +function d(s,e=250){let t;return function(...a){t!==void 0&&clearTimeout(t),t=setTimeout(()=>{s.apply(this,a)},e)}}function u(s){return s.replace(/([$`"\\!|&;<>*?{}()])/g,"\\$1")}var l=class s{DOM;funcList;static SELECTORS={container:".js-help-view",func:"[data-func]",addressInput:"[data-role='help-input-addr']",cmdModeSelect:"[data-role='help-select-mode']"};constructor(){this.DOM={el:document.querySelector(s.SELECTORS.container),funcs:[],addressInput:null,cmdModeSelect:null},this.funcList=[],this.DOM.el?this.init():console.warn("Help: Main container not found.")}init(){let{el:e}=this.DOM;e&&(this.DOM.funcs=Array.from(e.querySelectorAll(s.SELECTORS.func)),this.DOM.addressInput=e.querySelector(s.SELECTORS.addressInput),this.DOM.cmdModeSelect=e.querySelector(s.SELECTORS.cmdModeSelect),this.funcList=this.DOM.funcs.map(t=>new o(t)),this.restoreAddress(),this.bindEvents())}restoreAddress(){let{addressInput:e}=this.DOM;if(e){let t=localStorage.getItem("helpAddressInput");t&&(e.value=t,this.funcList.forEach(a=>a.updateAddr(t)))}}bindEvents(){let{addressInput:e,cmdModeSelect:t}=this.DOM,a=d(n=>{let r=n.value;localStorage.setItem("helpAddressInput",r),this.funcList.forEach(i=>i.updateAddr(r))},50);e?.addEventListener("input",()=>a(e)),t?.addEventListener("change",n=>{let r=n.target;this.funcList.forEach(i=>i.updateMode(r.value))})}},o=class s{DOM;funcName;static SELECTORS={address:"[data-role='help-code-address']",args:"[data-role='help-code-args']",mode:"[data-code-mode]",paramInput:"[data-role='help-param-input']"};constructor(e){this.DOM={el:e,addrs:Array.from(e.querySelectorAll(s.SELECTORS.address)),args:Array.from(e.querySelectorAll(s.SELECTORS.args)),modes:Array.from(e.querySelectorAll(s.SELECTORS.mode)),paramInputs:Array.from(e.querySelectorAll(s.SELECTORS.paramInput))},this.funcName=e.dataset.func||null,this.initializeArgs(),this.bindEvents()}static sanitizeArgsInput(e){let t=e.dataset.param||"",a=e.value.trim();return t||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:t,paramValue:a}}bindEvents(){let e=d((t,a)=>{t&&this.updateArg(t,a)},50);this.DOM.el.addEventListener("input",t=>{let a=t.target;if(a.dataset.role==="help-param-input"){let{paramName:n,paramValue:r}=s.sanitizeArgsInput(a);e(n,r)}})}initializeArgs(){this.DOM.paramInputs.forEach(e=>{let{paramName:t,paramValue:a}=s.sanitizeArgsInput(e);t&&this.updateArg(t,a)})}updateArg(e,t){let a=u(t);this.DOM.args.filter(n=>n.dataset.arg===e).forEach(n=>{n.textContent=a||""})}updateAddr(e){this.DOM.addrs.forEach(t=>{t.textContent=e.trim()||"ADDRESS"})}updateMode(e){this.DOM.modes.forEach(t=>{let a=t.dataset.codeMode===e;t.classList.toggle("inline",a),t.classList.toggle("hidden",!a),t.dataset.copyContent=a?`help-cmd-${this.funcName}`:""})}},m=()=>new l;export{m as default}; diff --git a/gno.land/pkg/gnoweb/public/js/searchbar.js b/gno.land/pkg/gnoweb/public/js/searchbar.js index e8012b9b6d9..424e8f75db1 100644 --- a/gno.land/pkg/gnoweb/public/js/searchbar.js +++ b/gno.land/pkg/gnoweb/public/js/searchbar.js @@ -1 +1 @@ -var n=class r{DOM;baseUrl;static SELECTORS={container:"#header-searchbar",inputSearch:"[data-role='header-input-search']",breadcrumb:"[data-role='header-breadcrumb-search']"};constructor(){this.DOM={el:document.querySelector(r.SELECTORS.container),inputSearch:null,breadcrumb:null},this.baseUrl=window.location.origin,this.DOM.el?this.init():console.warn("SearchBar: Main container not found.")}init(){let{el:e}=this.DOM;this.DOM.inputSearch=e?.querySelector(r.SELECTORS.inputSearch)??null,this.DOM.breadcrumb=e?.querySelector(r.SELECTORS.breadcrumb)??null,this.DOM.inputSearch||console.warn("SearchBar: Input element for search not found."),this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("submit",e=>{e.preventDefault(),this.searchUrl()})}searchUrl(){let e=this.DOM.inputSearch?.value.trim();if(e){let t=e;/^https?:\/\//i.test(t)||(t=`${this.baseUrl}${t.startsWith("/")?"":"/"}${t}`);try{window.location.href=new URL(t).href}catch{console.error("SearchBar: Invalid URL. Please enter a valid URL starting with http:// or https://.")}}else console.error("SearchBar: Please enter a URL to search.")}},i=()=>new n;export{i as default}; +var n=class r{DOM;baseUrl;static SELECTORS={container:".js-header-searchbar",inputSearch:"[data-role='header-input-search']",breadcrumb:"[data-role='header-breadcrumb-search']"};constructor(){this.DOM={el:document.querySelector(r.SELECTORS.container),inputSearch:null,breadcrumb:null},this.baseUrl=window.location.origin,this.DOM.el?this.init():console.warn("SearchBar: Main container not found.")}init(){let{el:e}=this.DOM;this.DOM.inputSearch=e?.querySelector(r.SELECTORS.inputSearch)??null,this.DOM.breadcrumb=e?.querySelector(r.SELECTORS.breadcrumb)??null,this.DOM.inputSearch||console.warn("SearchBar: Input element for search not found."),this.bindEvents()}bindEvents(){this.DOM.el?.addEventListener("submit",e=>{e.preventDefault(),this.searchUrl()})}searchUrl(){let e=this.DOM.inputSearch?.value.trim();if(e){let t=e;/^https?:\/\//i.test(t)||(t=`${this.baseUrl}${t.startsWith("/")?"":"/"}${t}`);try{window.location.href=new URL(t).href}catch{console.error("SearchBar: Invalid URL. Please enter a valid URL starting with http:// or https://.")}}else console.error("SearchBar: Please enter a URL to search.")}},i=()=>new n;export{i as default}; diff --git a/gno.land/pkg/gnoweb/public/js/utils.js b/gno.land/pkg/gnoweb/public/js/utils.js index e27fb93bc1c..ce96def444a 100644 --- a/gno.land/pkg/gnoweb/public/js/utils.js +++ b/gno.land/pkg/gnoweb/public/js/utils.js @@ -1 +1 @@ -function r(t,n=250){let e;return function(...i){e!==void 0&&clearTimeout(e),e=setTimeout(()=>{t.apply(this,i)},n)}}export{r as debounce}; +function i(e,n=250){let t;return function(...r){t!==void 0&&clearTimeout(t),t=setTimeout(()=>{e.apply(this,r)},n)}}function a(e){return e.replace(/([$`"\\!|&;<>*?{}()])/g,"\\$1")}export{i as debounce,a as escapeShellSpecialChars}; diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index 4ff1a266c0c..19994cb0047 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-view{overflow-wrap:break-word;padding-top:1.5rem;font-size:1rem}@media (min-width:51.25rem){.realm-view{padding-top:2.5rem}}.realm-view>:first-child{margin-top:0!important}.realm-view a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-view a:hover{text-decoration-line:underline}.realm-view h1,.realm-view h2,.realm-view h3,.realm-view h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-view h2,.realm-view h2 *{font-weight:700}.realm-view h3,.realm-view h3 *,.realm-view h4,.realm-view h4 *{font-weight:600}.realm-view h1+h2,.realm-view h2+h3,.realm-view h3+h4{margin-top:1rem}.realm-view h1{font-size:2.375rem;font-weight:700}.realm-view h2{font-size:1.5rem}.realm-view h3{margin-top:2.5rem;font-size:1.25rem}.realm-view h3,.realm-view h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-view p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-view strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-view strong *{font-weight:700}.realm-view em{font-style:oblique 14deg}.realm-view blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 14deg}.realm-view ol,.realm-view ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-view ol li,.realm-view ul li{margin-bottom:.5rem}.realm-view img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-view figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-view figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-view :not(pre)>code,.realm-view pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-view pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-view hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-view table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-view td,.realm-view th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-view th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-view caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-view q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 14deg;quotes:"“" "”" "‘" "’"}.realm-view q:after,.realm-view q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-view q:after{content:close-quote}.realm-view q:before{content:open-quote}.realm-view q:after{content:close-quote}.realm-view ol ol,.realm-view ol ul,.realm-view ul ol,.realm-view ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-view ul{list-style-type:disc}.realm-view ol{list-style-type:decimal}.realm-view abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-view details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-view summary{cursor:pointer;font-weight:700}.realm-view a code{color:inherit}.realm-view video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-view math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-view small{font-size:.875rem}.realm-view del{text-decoration-line:line-through}.realm-view sub{vertical-align:sub;font-size:.75rem}.realm-view sup{vertical-align:super;font-size:.75rem}.realm-view button,.realm-view input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-view{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-view>pre a:hover{text-decoration-line:none}main :is(.realm-view,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-view,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-view,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.dev-mode .toc-expend-btn{cursor:pointer;border-width:1px;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.dev-mode .toc-expend-btn:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}@media (min-width:51.25rem){.dev-mode .toc-expend-btn{border-style:none;background-color:transparent}}.dev-mode #sidebar-summary{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}@media (min-width:51.25rem){.dev-mode #sidebar-summary{background-color:transparent}}.dev-mode .toc-nav{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-1{bottom:.25rem}.left-0{left:0}.right-0{right:0}.right-2{right:.5rem}.right-3{right:.75rem}.right-px{right:1px}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.top-px{top:1px}.z-1{z-index:1}.z-max{z-index:9999}.order-2{order:2}.order-3{order:3}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[calc\(100\%-2px\)\]{height:calc(100% - 2px)}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-0{width:0}.w-10{width:2.5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-72{width:18rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-4{min-width:1rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-x-full{--tw-translate-x:-100%}.-translate-x-full,.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.rounded-r{border-top-right-radius:.375rem;border-bottom-right-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.border-transparent{border-color:transparent}.bg-current{background-color:currentColor}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(124 124 124/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.text-center{text-align:center}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.text-gray-100{--tw-text-opacity:1;color:rgb(226 226 226/var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.opacity-0{opacity:0}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.field-content{field-sizing:content}@supports not (field-sizing:content){.focus-no-field-sizing\:w-20:focus{width:5rem!important}}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:pt-0\.5:before{content:var(--tw-content);padding-top:.125rem}.before\:leading-normal:before{content:var(--tw-content);line-height:1.5}.before\:text-gray-400:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.before\:content-\[\'\&\'\]:before{--tw-content:"&";content:var(--tw-content)}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'\?\'\]:before{--tw-content:"?";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:mt-8:first-child{margin-top:2rem}.first\:border-t:first-child{border-top-width:1px}.focus-within\:border-gray-400:focus-within{--tw-border-opacity:1;border-color:rgb(124 124 124/var(--tw-border-opacity))}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:border-gray-400:hover{--tw-border-opacity:1;border-color:rgb(124 124 124/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:w-min:focus{width:-moz-min-content;width:min-content}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:opacity-100:focus{opacity:1}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:visible{visibility:visible}.peer:checked~.peer-checked\:opacity-100{opacity:1}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:-moz-placeholder-shown~.peer-placeholder-shown\:hidden{display:none}.peer:placeholder-shown~.peer-placeholder-shown\:hidden{display:none}.peer:focus-within~.peer-focus-within\:hidden{display:none}.peer:focus~.peer-focus\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\[data-role\=\'header-input-search\'\]\:focus-within\]\:border-gray-300:has([data-role=header-input-search]:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\[data-role\=\'header-input-search\'\]\:focus-within\]\:text-gray-50:has([data-role=header-input-search]:focus-within){--tw-text-opacity:1;color:rgb(240 240 240/var(--tw-text-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:flex{display:flex}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}.md\:pr-8{padding-right:2rem}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:first\:mt-0:first-child{margin-top:0}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file diff --git a/gno.land/pkg/gnoweb/webclient.go b/gno.land/pkg/gnoweb/webclient.go index a1005baa0a5..3fbc0aabe6d 100644 --- a/gno.land/pkg/gnoweb/webclient.go +++ b/gno.land/pkg/gnoweb/webclient.go @@ -2,126 +2,52 @@ package gnoweb import ( "errors" - "fmt" "io" - "log/slog" - "path/filepath" - "strings" md "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types - "github.com/gnolang/gno/tm2/pkg/amino" - "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/text" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" ) -type WebClient struct { - logger *slog.Logger - client *client.RPCClient - md goldmark.Markdown -} - -func NewWebClient(log *slog.Logger, cl *client.RPCClient, m goldmark.Markdown) *WebClient { - m.Parser().AddOptions(parser.WithAutoHeadingID()) - return &WebClient{ - logger: log, - client: cl, - md: m, - } -} - -func (s *WebClient) Functions(pkgPath string) ([]vm.FunctionSignature, error) { - const qpath = "vm/qfuncs" - - args := fmt.Sprintf("gno.land/%s", strings.Trim(pkgPath, "/")) - res, err := s.query(qpath, []byte(args)) - if err != nil { - return nil, fmt.Errorf("unable query funcs list: %w", err) - } - - var fsigs vm.FunctionSignatures - if err := amino.UnmarshalJSON(res, &fsigs); err != nil { - s.logger.Warn("unable to unmarshal fsigs, client is probably outdated ?") - return nil, fmt.Errorf("unable to unamarshal fsigs: %w", err) - } - - return fsigs, nil -} - -func (s *WebClient) SourceFile(path, fileName string) ([]byte, error) { - const qpath = "vm/qfile" - - fileName = strings.TrimSpace(fileName) // sanitize filename - if fileName == "" { - return nil, errors.New("empty filename given") // XXX -> ErrXXX - } - - // XXX: move this into gnoclient ? - path = fmt.Sprintf("gno.land/%s", strings.Trim(path, "/")) - path = filepath.Join(path, fileName) - return s.query(qpath, []byte(path)) -} - -func (s *WebClient) Sources(path string) ([]string, error) { - const qpath = "vm/qfile" - - // XXX: move this into gnoclient - path = fmt.Sprintf("gno.land/%s", strings.Trim(path, "/")) - res, err := s.query(qpath, []byte(path)) - if err != nil { - return nil, err - } +var ( + ErrClientPathNotFound = errors.New("package not found") + ErrRenderNotDeclared = errors.New("render function not declared") + ErrClientBadRequest = errors.New("bad request") + ErrClientResponse = errors.New("node response error") +) - files := strings.Split(string(res), "\n") - return files, nil +type FileMeta struct { + Lines int + SizeKb float64 } -type Metadata struct { - *md.Toc +type HeadMeta struct { + Title string + Description string + Canonical string } -func (s *WebClient) Render(w io.Writer, pkgPath string, args string) (*Metadata, error) { - const qpath = "vm/qrender" - - data := []byte(gnoPath(pkgPath, args)) - rawres, err := s.query(qpath, data) - if err != nil { - return nil, err - } - - doc := s.md.Parser().Parse(text.NewReader(rawres)) - if err := s.md.Renderer().Render(w, rawres, doc); err != nil { - return nil, fmt.Errorf("unable render real %q: %w", data, err) - } - - var meta Metadata - meta.Toc, err = md.TocInspect(doc, rawres, md.TocOptions{MaxDepth: 6, MinDepth: 2}) - if err != nil { - s.logger.Warn("unable to inspect for toc elements", "err", err) - } - - return &meta, nil +type RealmMeta struct { + Toc md.Toc + Head HeadMeta } -func (s *WebClient) query(qpath string, data []byte) ([]byte, error) { - s.logger.Info("query", "qpath", qpath, "data", string(data)) +// WebClient is an interface for interacting with package and node resources. +type WebClient interface { + // RenderRealm renders the content of a realm from a given path and + // arguments into the giver `writer`. The method should ensures the rendered + // content is safely handled and formatted. + RenderRealm(w io.Writer, path string, args string) (*RealmMeta, error) - qres, err := s.client.ABCIQuery(qpath, data) - if err != nil { - s.logger.Error("request error", "path", qpath, "data", string(data), "error", err) - return nil, fmt.Errorf("unable to query path %q: %w", qpath, err) - } - if qres.Response.Error != nil { - s.logger.Error("response error", "path", qpath, "log", qres.Response.Log) - return nil, qres.Response.Error - } + // SourceFile fetches and writes the source file from a given + // package path and file name. The method should ensures the source + // file's content is safely handled and formatted. + SourceFile(w io.Writer, pkgPath, fileName string) (*FileMeta, error) - return qres.Response.Data, nil -} + // Functions retrieves a list of function signatures from a + // specified package path. + Functions(path string) ([]vm.FunctionSignature, error) -func gnoPath(pkgPath, args string) string { - pkgPath = strings.Trim(pkgPath, "/") - return fmt.Sprintf("gno.land/%s:%s", pkgPath, args) + // Sources lists all source files available in a specified + // package path. + Sources(path string) ([]string, error) } diff --git a/gno.land/pkg/gnoweb/webclient_html.go b/gno.land/pkg/gnoweb/webclient_html.go new file mode 100644 index 00000000000..7d6bf075645 --- /dev/null +++ b/gno.land/pkg/gnoweb/webclient_html.go @@ -0,0 +1,285 @@ +package gnoweb + +import ( + "errors" + "fmt" + "io" + "log/slog" + gopath "path" + "strings" + + "github.com/alecthomas/chroma/v2" + chromahtml "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/alecthomas/chroma/v2/lexers" + "github.com/alecthomas/chroma/v2/styles" + md "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/yuin/goldmark" + markdown "github.com/yuin/goldmark-highlighting/v2" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" +) + +var chromaDefaultStyle = styles.Get("friendly") + +type HTMLWebClientConfig struct { + Domain string + RPCClient *client.RPCClient + ChromaStyle *chroma.Style + ChromaHTMLOptions []chromahtml.Option + GoldmarkOptions []goldmark.Option +} + +// NewDefaultHTMLWebClientConfig initializes a WebClientConfig with default settings. +// It sets up goldmark Markdown parsing options and default domain and highlighter. +func NewDefaultHTMLWebClientConfig(client *client.RPCClient) *HTMLWebClientConfig { + chromaOptions := []chromahtml.Option{ + chromahtml.WithLineNumbers(true), + chromahtml.WithLinkableLineNumbers(true, "L"), + chromahtml.WithClasses(true), + chromahtml.ClassPrefix("chroma-"), + } + + goldmarkOptions := []goldmark.Option{ + goldmark.WithParserOptions(parser.WithAutoHeadingID()), + goldmark.WithExtensions( + md.NewMetadata(), + markdown.NewHighlighting( + markdown.WithFormatOptions(chromaOptions...), + ), + extension.Strikethrough, + extension.Table, + ), + } + + return &HTMLWebClientConfig{ + Domain: "gno.land", + GoldmarkOptions: goldmarkOptions, + ChromaHTMLOptions: chromaOptions, + ChromaStyle: chromaDefaultStyle, + RPCClient: client, + } +} + +type HTMLWebClient struct { + Markdown goldmark.Markdown + Formatter *chromahtml.Formatter + + domain string + logger *slog.Logger + client *client.RPCClient + chromaStyle *chroma.Style +} + +// NewHTMLClient creates a new instance of WebClient. +// It requires a configured logger and WebClientConfig. +func NewHTMLClient(log *slog.Logger, cfg *HTMLWebClientConfig) *HTMLWebClient { + return &HTMLWebClient{ + // XXX: Possibly consider exporting this in a single interface logic. + // For now it's easier to manager all this in one place + Markdown: goldmark.New(cfg.GoldmarkOptions...), + Formatter: chromahtml.New(cfg.ChromaHTMLOptions...), + + logger: log, + domain: cfg.Domain, + client: cfg.RPCClient, + chromaStyle: cfg.ChromaStyle, + } +} + +// Functions retrieves a list of function signatures from a +// specified package path. +func (s *HTMLWebClient) Functions(pkgPath string) ([]vm.FunctionSignature, error) { + const qpath = "vm/qfuncs" + + args := fmt.Sprintf("%s/%s", s.domain, strings.Trim(pkgPath, "/")) + res, err := s.query(qpath, []byte(args)) + if err != nil { + return nil, fmt.Errorf("unable to query func list: %w", err) + } + + var fsigs vm.FunctionSignatures + if err := amino.UnmarshalJSON(res, &fsigs); err != nil { + s.logger.Warn("unable to unmarshal function signatures, client is probably outdated") + return nil, fmt.Errorf("unable to unmarshal function signatures: %w", err) + } + + return fsigs, nil +} + +// SourceFile fetches and writes the source file from a given +// package path and file name to the provided writer. It uses +// Chroma for syntax highlighting source. +func (s *HTMLWebClient) SourceFile(w io.Writer, path, fileName string) (*FileMeta, error) { + const qpath = "vm/qfile" + + fileName = strings.TrimSpace(fileName) + if fileName == "" { + return nil, errors.New("empty filename given") // XXX: Consider creating a specific error variable + } + + // XXX: Consider moving this into gnoclient + fullPath := gopath.Join(s.domain, strings.Trim(path, "/"), fileName) + + source, err := s.query(qpath, []byte(fullPath)) + if err != nil { + // XXX: this is a bit ugly, we should make the keeper return an + // assertable error. + if strings.Contains(err.Error(), "not available") { + return nil, ErrClientPathNotFound + } + + return nil, err + } + + fileMeta := FileMeta{ + Lines: strings.Count(string(source), "\n"), + SizeKb: float64(len(source)) / 1024.0, + } + + // Use Chroma for syntax highlighting + if err := s.FormatSource(w, fileName, source); err != nil { + return nil, err + } + + return &fileMeta, nil +} + +// Sources lists all source files available in a specified +// package path by querying the RPC client. +func (s *HTMLWebClient) Sources(path string) ([]string, error) { + const qpath = "vm/qfile" + + // XXX: Consider moving this into gnoclient + pkgPath := strings.Trim(path, "/") + fullPath := fmt.Sprintf("%s/%s", s.domain, pkgPath) + res, err := s.query(qpath, []byte(fullPath)) + if err != nil { + // XXX: this is a bit ugly, we should make the keeper return an + // assertable error. + if strings.Contains(err.Error(), "not available") { + return nil, ErrClientPathNotFound + } + + return nil, err + } + + files := strings.Split(strings.TrimSpace(string(res)), "\n") + return files, nil +} + +// extractHeadMeta extracts optional head metadata from the provided metaData map +// and returns a HeadMeta struct. All fields ("Title", "Description", "Canonical") +// are optional; if a field is not present or not a string, it will be empty. +func extractHeadMeta(metaData map[string]interface{}) HeadMeta { + hm := HeadMeta{} + if title, ok := metaData["Title"].(string); ok { + hm.Title = title + } + if desc, ok := metaData["Description"].(string); ok { + hm.Description = desc + } + if canonical, ok := metaData["Canonical"].(string); ok { + hm.Canonical = canonical + } + return hm +} + +// RenderRealm renders the content of a realm from a given path +// and arguments into the provided writer. It uses Goldmark for +// Markdown processing to generate HTML content. +func (s *HTMLWebClient) RenderRealm(w io.Writer, pkgPath string, args string) (*RealmMeta, error) { + const qpath = "vm/qrender" + + pkgPath = strings.Trim(pkgPath, "/") + data := fmt.Sprintf("%s/%s:%s", s.domain, pkgPath, args) + + rawres, err := s.query(qpath, []byte(data)) + if err != nil { + return nil, err + } + + // Use Goldmark for Markdown parsing + context := parser.NewContext() + doc := s.Markdown.Parser().Parse(text.NewReader(rawres), parser.WithContext(context)) + metaData := md.Get(context) + + if err := s.Markdown.Renderer().Render(w, rawres, doc); err != nil { + return nil, fmt.Errorf("unable to render realm %q: %w", data, err) + } + + var meta RealmMeta + meta.Toc, err = md.TocInspect(doc, rawres, md.TocOptions{MaxDepth: 6, MinDepth: 2}) + if err != nil { + s.logger.Warn("unable to inspect for TOC elements", "error", err) + } + + meta.Head = extractHeadMeta(metaData) + + return &meta, nil +} + +// query sends a query to the RPC client and returns the response +// data. +func (s *HTMLWebClient) query(qpath string, data []byte) ([]byte, error) { + s.logger.Info("query", "path", qpath, "data", string(data)) + + qres, err := s.client.ABCIQuery(qpath, data) + if err != nil { + s.logger.Debug("request error", "path", qpath, "data", string(data), "error", err) + return nil, fmt.Errorf("%w: %s", ErrClientBadRequest, err.Error()) + } + + if err = qres.Response.Error; err != nil { + if errors.Is(err, vm.InvalidPkgPathError{}) { + return nil, ErrClientPathNotFound + } + + if errors.Is(err, vm.NoRenderDeclError{}) { + return nil, ErrRenderNotDeclared + } + + s.logger.Error("response error", "path", qpath, "log", qres.Response.Log) + return nil, fmt.Errorf("%w: %s", ErrClientResponse, err.Error()) + } + + return qres.Response.Data, nil +} + +func (s *HTMLWebClient) FormatSource(w io.Writer, fileName string, src []byte) error { + var lexer chroma.Lexer + + // Determine the lexer to be used based on the file extension. + switch strings.ToLower(gopath.Ext(fileName)) { + case ".gno": + lexer = lexers.Get("go") + case ".md": + lexer = lexers.Get("markdown") + case ".mod": + lexer = lexers.Get("gomod") + default: + lexer = lexers.Get("txt") // Unsupported file type, default to plain text. + } + + if lexer == nil { + return fmt.Errorf("unsupported lexer for file %q", fileName) + } + + iterator, err := lexer.Tokenise(nil, string(src)) + if err != nil { + return fmt.Errorf("unable to tokenise %q: %w", fileName, err) + } + + if err := s.Formatter.Format(w, s.chromaStyle, iterator); err != nil { + return fmt.Errorf("unable to format source file %q: %w", fileName, err) + } + + return nil +} + +func (s *HTMLWebClient) WriteFormatterCSS(w io.Writer) error { + return s.Formatter.WriteCSS(w, s.chromaStyle) +} diff --git a/gno.land/pkg/gnoweb/webclient_mock.go b/gno.land/pkg/gnoweb/webclient_mock.go new file mode 100644 index 00000000000..8a037c181e0 --- /dev/null +++ b/gno.land/pkg/gnoweb/webclient_mock.go @@ -0,0 +1,114 @@ +package gnoweb + +import ( + "bytes" + "fmt" + "io" + "sort" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" +) + +// MockPackage represents a mock package with files and function signatures. +type MockPackage struct { + Path string + Domain string + Files map[string]string // filename -> body + Functions []vm.FunctionSignature +} + +// MockWebClient is a mock implementation of the Client interface. +type MockWebClient struct { + Packages map[string]*MockPackage // path -> package +} + +func NewMockWebClient(pkgs ...*MockPackage) *MockWebClient { + mpkgs := make(map[string]*MockPackage) + for _, pkg := range pkgs { + mpkgs[pkg.Path] = pkg + } + + return &MockWebClient{Packages: mpkgs} +} + +// RenderRealm simulates rendering a package by writing its content to the writer. +func (m *MockWebClient) RenderRealm(w io.Writer, path string, args string) (*RealmMeta, error) { + pkg, exists := m.Packages[path] + if !exists { + return nil, ErrClientPathNotFound + } + + if !pkgHasRender(pkg) { + return nil, ErrRenderNotDeclared + } + + // Write to the realm render + fmt.Fprintf(w, "[%s]%s:", pkg.Domain, pkg.Path) + + // Return a dummy RealmMeta for simplicity + return &RealmMeta{}, nil +} + +// SourceFile simulates retrieving a source file's metadata. +func (m *MockWebClient) SourceFile(w io.Writer, pkgPath, fileName string) (*FileMeta, error) { + pkg, exists := m.Packages[pkgPath] + if !exists { + return nil, ErrClientPathNotFound + } + + if body, ok := pkg.Files[fileName]; ok { + w.Write([]byte(body)) + return &FileMeta{ + Lines: len(bytes.Split([]byte(body), []byte("\n"))), + SizeKb: float64(len(body)) / 1024.0, + }, nil + } + + return nil, ErrClientPathNotFound +} + +// Functions simulates retrieving function signatures from a package. +func (m *MockWebClient) Functions(path string) ([]vm.FunctionSignature, error) { + pkg, exists := m.Packages[path] + if !exists { + return nil, ErrClientPathNotFound + } + + return pkg.Functions, nil +} + +// Sources simulates listing all source files in a package. +func (m *MockWebClient) Sources(path string) ([]string, error) { + pkg, exists := m.Packages[path] + if !exists { + return nil, ErrClientPathNotFound + } + + fileNames := make([]string, 0, len(pkg.Files)) + for file := range pkg.Files { + fileNames = append(fileNames, file) + } + + // Sort for consistency + sort.Strings(fileNames) + + return fileNames, nil +} + +func pkgHasRender(pkg *MockPackage) bool { + if len(pkg.Functions) == 0 { + return false + } + + for _, fn := range pkg.Functions { + if fn.FuncName == "Render" && + len(fn.Params) == 1 && + len(fn.Results) == 1 && + fn.Params[0].Type == "string" && + fn.Results[0].Type == "string" { + return true + } + } + + return false +} diff --git a/gno.land/pkg/gnoweb/url.go b/gno.land/pkg/gnoweb/weburl/url.go similarity index 84% rename from gno.land/pkg/gnoweb/url.go rename to gno.land/pkg/gnoweb/weburl/url.go index 9127225d490..d16819c649c 100644 --- a/gno.land/pkg/gnoweb/url.go +++ b/gno.land/pkg/gnoweb/weburl/url.go @@ -1,10 +1,10 @@ -package gnoweb +package weburl import ( "errors" "fmt" "net/url" - "path/filepath" + gopath "path" "regexp" "slices" "strings" @@ -21,7 +21,7 @@ type GnoURL struct { // gno.land/r/demo/users/render.gno:jae$help&a=b?c=d Domain string // gno.land - Path string // /r/demo/users + Path string // /r/gnoland/users/v1 Args string // jae WebQuery url.Values // help&a=b Query url.Values // c=d @@ -97,20 +97,12 @@ func (gnoURL GnoURL) Encode(encodeFlags EncodeFlag) string { if encodeFlags.Has(EncodeWebQuery) && len(gnoURL.WebQuery) > 0 { urlstr.WriteRune('$') - if noEscape { - urlstr.WriteString(NoEscapeQuery(gnoURL.WebQuery)) - } else { - urlstr.WriteString(gnoURL.WebQuery.Encode()) - } + urlstr.WriteString(EncodeValues(gnoURL.WebQuery, !noEscape)) } if encodeFlags.Has(EncodeQuery) && len(gnoURL.Query) > 0 { urlstr.WriteRune('?') - if noEscape { - urlstr.WriteString(NoEscapeQuery(gnoURL.Query)) - } else { - urlstr.WriteString(gnoURL.Query.Encode()) - } + urlstr.WriteString(EncodeValues(gnoURL.Query, !noEscape)) } return urlstr.String() @@ -134,13 +126,13 @@ func (gnoURL GnoURL) EncodeArgs() string { // EncodeURL encodes the path, arguments, and query parameters into a string. // This function provides the full representation of the URL without the web query. func (gnoURL GnoURL) EncodeURL() string { - return gnoURL.Encode(EncodePath | EncodeArgs | EncodeQuery) + return gnoURL.Encode(EncodePath | EncodeArgs | EncodeQuery | EncodeNoEscape) } // EncodeWebURL encodes the path, package arguments, web query, and query into a string. // This function provides the full representation of the URL. func (gnoURL GnoURL) EncodeWebURL() string { - return gnoURL.Encode(EncodePath | EncodeArgs | EncodeWebQuery | EncodeQuery) + return gnoURL.Encode(EncodePath | EncodeArgs | EncodeWebQuery | EncodeQuery | EncodeNoEscape) } // IsPure checks if the URL path represents a pure path. @@ -187,8 +179,8 @@ func ParseGnoURL(u *url.URL) (*GnoURL, error) { // A file is considered as one that either ends with an extension or // contains an uppercase rune - ext := filepath.Ext(upath) - base := filepath.Base(upath) + ext := gopath.Ext(upath) + base := gopath.Base(upath) if ext != "" || strings.ToLower(base) != base { file = base upath = strings.TrimSuffix(upath, base) @@ -226,11 +218,11 @@ func ParseGnoURL(u *url.URL) (*GnoURL, error) { }, nil } -// NoEscapeQuery generates a URL-encoded query string from the given url.Values, -// without escaping the keys and values. The query parameters are sorted by key. -func NoEscapeQuery(v url.Values) string { - // Encode encodes the values into “URL encoded” form - // ("bar=baz&foo=quux") sorted by key. +// EncodeValues generates a URL-encoded query string from the given url.Values. +// This function is a modified version of Go's `url.Values.Encode()`: https://pkg.go.dev/net/url#Values.Encode +// It takes an additional `escape` boolean argument that disables escaping on keys and values. +// Additionally, if an empty string value is passed, it omits the `=` sign, resulting in `?key` instead of `?key=` to enhance URL readability. +func EncodeValues(v url.Values, escape bool) string { if len(v) == 0 { return "" } @@ -240,16 +232,29 @@ func NoEscapeQuery(v url.Values) string { keys = append(keys, k) } slices.Sort(keys) + for _, k := range keys { vs := v[k] - keyEscaped := k + keyEncoded := k + if escape { + keyEncoded = url.QueryEscape(k) + } for _, v := range vs { if buf.Len() > 0 { buf.WriteByte('&') } - buf.WriteString(keyEscaped) + buf.WriteString(keyEncoded) + + if len(v) == 0 { + continue // Skip `=` for empty values + } + buf.WriteByte('=') - buf.WriteString(v) + if escape { + buf.WriteString(url.QueryEscape(v)) + } else { + buf.WriteString(v) + } } } return buf.String() diff --git a/gno.land/pkg/gnoweb/url_test.go b/gno.land/pkg/gnoweb/weburl/url_test.go similarity index 87% rename from gno.land/pkg/gnoweb/url_test.go rename to gno.land/pkg/gnoweb/weburl/url_test.go index 7a491eaa149..682832f5b0d 100644 --- a/gno.land/pkg/gnoweb/url_test.go +++ b/gno.land/pkg/gnoweb/weburl/url_test.go @@ -1,4 +1,4 @@ -package gnoweb +package weburl import ( "net/url" @@ -301,7 +301,7 @@ func TestEncode(t *testing.T) { }, }, EncodeFlags: EncodeWebQuery | EncodeNoEscape, - Expected: "$fun$c=B$ ar&help=", + Expected: "$fun$c=B$ ar&help", }, { @@ -450,6 +450,69 @@ func TestEncode(t *testing.T) { EncodeFlags: EncodePath | EncodeArgs | EncodeQuery, Expected: "/r/demo/foo:example?hello=42", }, + + { + Name: "WebQuery with empty value", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + WebQuery: url.Values{ + "source": {""}, + }, + }, + EncodeFlags: EncodePath | EncodeWebQuery | EncodeNoEscape, + Expected: "/r/demo/foo$source", + }, + + { + Name: "WebQuery with nil", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + WebQuery: url.Values{ + "debug": nil, + }, + }, + EncodeFlags: EncodePath | EncodeWebQuery, + Expected: "/r/demo/foo$", + }, + + { + Name: "WebQuery with regular value", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + WebQuery: url.Values{ + "key": {"value"}, + }, + }, + EncodeFlags: EncodePath | EncodeWebQuery, + Expected: "/r/demo/foo$key=value", + }, + + { + Name: "WebQuery mixing empty and nil and filled values", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + WebQuery: url.Values{ + "source": {""}, + "debug": nil, + "user": {"Alice"}, + }, + }, + EncodeFlags: EncodePath | EncodeWebQuery, + Expected: "/r/demo/foo$source&user=Alice", + }, + + { + Name: "WebQuery mixing nil and filled values", + GnoURL: GnoURL{ + Path: "/r/demo/foo", + WebQuery: url.Values{ + "debug": nil, + "user": {"Alice"}, + }, + }, + EncodeFlags: EncodePath | EncodeWebQuery, + Expected: "/r/demo/foo$user=Alice", + }, } for _, tc := range testCases { diff --git a/gno.land/pkg/integration/node_testing.go b/gno.land/pkg/integration/node_testing.go index edcf53de5d3..6cb8819f2aa 100644 --- a/gno.land/pkg/integration/node_testing.go +++ b/gno.land/pkg/integration/node_testing.go @@ -8,12 +8,16 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/db/memdb" + "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" "github.com/stretchr/testify/require" ) @@ -59,18 +63,15 @@ func TestingNodeConfig(t TestingTS, gnoroot string, additionalTxs ...gnoland.TxW cfg.SkipGenesisVerification = true creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 - - params := LoadDefaultGenesisParamFile(t, gnoroot) balances := LoadDefaultGenesisBalanceFile(t, gnoroot) txs := make([]gnoland.TxWithMetadata, 0) txs = append(txs, LoadDefaultPackages(t, creator, gnoroot)...) txs = append(txs, additionalTxs...) - - cfg.Genesis.AppState = gnoland.GnoGenesisState{ - Balances: balances, - Txs: txs, - Params: params, - } + ggs := cfg.Genesis.AppState.(gnoland.GnoGenesisState) + ggs.Balances = balances + ggs.Txs = txs + LoadDefaultGenesisParamFile(t, gnoroot, &ggs) + cfg.Genesis.AppState = ggs return cfg, creator } @@ -97,16 +98,31 @@ func TestingMinimalNodeConfig(gnoroot string) *gnoland.InMemoryNodeConfig { } } +// XXX shouldn't this use GenerateTestingGenesisState? func DefaultTestingGenesisConfig(gnoroot string, self crypto.PubKey, tmconfig *tmcfg.Config) *bft.GenesisDoc { + authGen := auth.DefaultGenesisState() + authGen.Params.UnrestrictedAddrs = []crypto.Address{crypto.MustAddressFromString(DefaultAccount_Address)} + authGen.Params.InitialGasPrice = std.GasPrice{Gas: 0, Price: std.Coin{Amount: 0, Denom: "ugnot"}} + genState := gnoland.DefaultGenState() + genState.Balances = []gnoland.Balance{ + { + Address: crypto.MustAddressFromString(DefaultAccount_Address), + Amount: std.MustParseCoins(ugnot.ValueString(10000000000000)), + }, + } + genState.Txs = []gnoland.TxWithMetadata{} + genState.Auth = authGen + genState.Bank = bank.DefaultGenesisState() + genState.VM = vmm.DefaultGenesisState() return &bft.GenesisDoc{ GenesisTime: time.Now(), ChainID: tmconfig.ChainID(), ConsensusParams: abci.ConsensusParams{ Block: &abci.BlockParams{ - MaxTxBytes: 1_000_000, // 1MB, - MaxDataBytes: 2_000_000, // 2MB, - MaxGas: 100_000_000, // 100M gas - TimeIotaMS: 100, // 100ms + MaxTxBytes: 1_000_000, // 1MB, + MaxDataBytes: 2_000_000, // 2MB, + MaxGas: 3_000_000_000, // 3B gas + TimeIotaMS: 100, // 100ms }, }, Validators: []bft.GenesisValidator{ @@ -117,16 +133,7 @@ func DefaultTestingGenesisConfig(gnoroot string, self crypto.PubKey, tmconfig *t Name: "self", }, }, - AppState: gnoland.GnoGenesisState{ - Balances: []gnoland.Balance{ - { - Address: crypto.MustAddressFromString(DefaultAccount_Address), - Amount: std.MustParseCoins(ugnot.ValueString(10_000_000_000_000)), - }, - }, - Txs: []gnoland.TxWithMetadata{}, - Params: []gnoland.Param{}, - }, + AppState: genState, } } @@ -152,13 +159,11 @@ func LoadDefaultGenesisBalanceFile(t TestingTS, gnoroot string) []gnoland.Balanc } // LoadDefaultGenesisParamFile loads the default genesis balance file for testing. -func LoadDefaultGenesisParamFile(t TestingTS, gnoroot string) []gnoland.Param { +func LoadDefaultGenesisParamFile(t TestingTS, gnoroot string, ggs *gnoland.GnoGenesisState) { paramFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_params.toml") - genesisParams, err := gnoland.LoadGenesisParamsFile(paramFile) + err := gnoland.LoadGenesisParamsFile(paramFile, ggs) require.NoError(t, err) - - return genesisParams } // LoadDefaultGenesisTXsFile loads the default genesis transactions file for testing. @@ -186,3 +191,33 @@ func DefaultTestingTMConfig(gnoroot string) *tmcfg.Config { tmconfig.P2P.ListenAddress = defaultListner return tmconfig } + +func GenerateTestingGenesisState(creator crypto.PrivKey, pkgs ...gnovm.MemPackage) gnoland.GnoGenesisState { + txs := make([]gnoland.TxWithMetadata, len(pkgs)) + for i, pkg := range pkgs { + // Create transaction + var tx std.Tx + tx.Fee = std.Fee{GasWanted: 1e6, GasFee: std.Coin{Amount: 1e6, Denom: "ugnot"}} + tx.Msgs = []std.Msg{ + vmm.MsgAddPackage{ + Creator: creator.PubKey().Address(), + Package: &pkg, + }, + } + + tx.Signatures = make([]std.Signature, len(tx.GetSigners())) + txs[i] = gnoland.TxWithMetadata{Tx: tx} + } + + gnoland.SignGenesisTxs(txs, creator, "tendermint_test") + return gnoland.GnoGenesisState{ + Txs: txs, + Balances: []gnoland.Balance{{ + Address: creator.PubKey().Address(), + Amount: std.MustParseCoins(ugnot.ValueString(10_000_000_000_000)), + }}, + Auth: auth.DefaultGenesisState(), + Bank: bank.DefaultGenesisState(), + VM: vmm.DefaultGenesisState(), + } +} diff --git a/gno.land/pkg/integration/node_testing_test.go b/gno.land/pkg/integration/node_testing_test.go new file mode 100644 index 00000000000..96b40bc0ec7 --- /dev/null +++ b/gno.land/pkg/integration/node_testing_test.go @@ -0,0 +1,75 @@ +package integration + +import ( + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenerateTestingGenesisState(t *testing.T) { + // Generate a test private key and address + privKey := secp256k1.GenPrivKey() + creatorAddr := privKey.PubKey().Address() + + // Create sample packages + pkg1 := gnovm.MemPackage{ + Name: "pkg1", + Path: "pkg1", + Files: []*gnovm.MemFile{ + {Name: "file.gno", Body: "package1"}, + }, + } + pkg2 := gnovm.MemPackage{ + Name: "pkg2", + Path: "pkg2", + Files: []*gnovm.MemFile{ + {Name: "file.gno", Body: "package2"}, + }, + } + + t.Run("single package genesis", func(t *testing.T) { + genesis := GenerateTestingGenesisState(privKey, pkg1) + + // Verify transactions + require.Len(t, genesis.Txs, 1) + tx := genesis.Txs[0].Tx + + // Check the transaction's message + require.Len(t, tx.Msgs, 1) + msg, ok := tx.Msgs[0].(vm.MsgAddPackage) + require.True(t, ok, "expected MsgAddPackage") + assert.Equal(t, pkg1, *msg.Package, "package mismatch") + + // Verify transaction signatures + require.Len(t, tx.Signatures, 1) + assert.NotEmpty(t, tx.Signatures[0], "signature should not be empty") + + // Verify balances + require.Len(t, genesis.Balances, 1) + balance := genesis.Balances[0] + assert.Equal(t, creatorAddr, balance.Address) + assert.Equal(t, std.MustParseCoins(ugnot.ValueString(10_000_000_000_000)), balance.Amount) + }) + + t.Run("multiple packages genesis", func(t *testing.T) { + genesis := GenerateTestingGenesisState(privKey, pkg1, pkg2) + + // Verify two transactions are created + require.Len(t, genesis.Txs, 2) + + // Check each transaction's package + for i, expectedPkg := range []gnovm.MemPackage{pkg1, pkg2} { + tx := genesis.Txs[i].Tx + require.Len(t, tx.Msgs, 1) + msg, ok := tx.Msgs[0].(vm.MsgAddPackage) + require.True(t, ok, "expected MsgAddPackage") + assert.Equal(t, expectedPkg, *msg.Package, "package mismatch in tx %d", i) + } + }) +} diff --git a/gno.land/pkg/integration/pkgloader.go b/gno.land/pkg/integration/pkgloader.go index 71b1491b2a8..5968ce2b4ef 100644 --- a/gno.land/pkg/integration/pkgloader.go +++ b/gno.land/pkg/integration/pkgloader.go @@ -134,7 +134,7 @@ func (pl *PkgsLoader) LoadPackage(modroot string, path, name string) error { if err != nil { return fmt.Errorf("unable to load package imports in %q: %w", currentPkg.Dir, err) } - imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest, packages.FileKindFiletest) + imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest) for _, imp := range imports { if imp.PkgPath == currentPkg.Name || gnolang.IsStdlib(imp.PkgPath) { continue diff --git a/gno.land/pkg/integration/process.go b/gno.land/pkg/integration/process.go index 839004ca1f3..b8bec17ccda 100644 --- a/gno.land/pkg/integration/process.go +++ b/gno.land/pkg/integration/process.go @@ -423,6 +423,7 @@ func isAllZero(key [64]byte) bool { return true } +// XXX why is this needed? type MarshalableGenesisDoc bft.GenesisDoc func NewMarshalableGenesisDoc(doc *bft.GenesisDoc) *MarshalableGenesisDoc { diff --git a/gno.land/pkg/integration/process_test.go b/gno.land/pkg/integration/process_test.go index b8768ad0e63..56ae16ff596 100644 --- a/gno.land/pkg/integration/process_test.go +++ b/gno.land/pkg/integration/process_test.go @@ -25,7 +25,6 @@ func TestMain(m *testing.M) { // Check if the embedded command should be executed if !*runCommand { - fmt.Println("Running tests...") os.Exit(m.Run()) } diff --git a/gno.land/pkg/integration/testdata/addpkg_invalid.txtar b/gno.land/pkg/integration/testdata/addpkg_invalid.txtar index bcec784a530..cb8edd858d6 100644 --- a/gno.land/pkg/integration/testdata/addpkg_invalid.txtar +++ b/gno.land/pkg/integration/testdata/addpkg_invalid.txtar @@ -4,7 +4,7 @@ gnoland start # add bar package located in $WORK directory as gno.land/r/foobar/bar -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 10000000ugnot -gas-wanted 20000000 -broadcast -chainid=tendermint_test test1 # check error message stdout 'TX HASH: ' diff --git a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar index 2cfd00acda4..ce844828cd7 100644 --- a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar +++ b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar @@ -1,20 +1,16 @@ -loadpkg gno.land/r/demo/users -loadpkg gno.land/r/sys/users +loadpkg gno.land/r/sys/names +loadpkg gno.land/r/gnoland/users/v1 adduser admin adduser gui -patchpkg "g1manfred47kzduec920z88wfr64ylksmdcedlf5" $admin_user_addr # use our custom admin +patchpkg "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" $admin_user_addr # use our custom admin gnoland start -## When `sys/users` is disabled - -# Should be disabled by default, addpkg should work by default - -# Check if sys/users is disabled -# gui call -> sys/users.IsEnable -gnokey maketx call -pkgpath gno.land/r/sys/users -func IsEnabled -gas-fee 100000ugnot -gas-wanted 200000 -broadcast -chainid tendermint_test gui +# Check if sys/names is disabled +# gui call -> sys/names.IsEnabled +gnokey maketx call -pkgpath gno.land/r/sys/names -func IsEnabled -gas-fee 100000ugnot -gas-wanted 200000 -broadcast -chainid tendermint_test gui stdout 'OK!' stdout 'false' @@ -28,27 +24,25 @@ stdout 'OK!' gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/randomname/mysuperpkg -gas-fee 1000000ugnot -gas-wanted 350000 -broadcast -chainid=tendermint_test gui stdout 'OK!' -## When `sys/users` is enabled - -# Enable `sys/users` -# admin call -> sys/users.AdminEnable -gnokey maketx call -pkgpath gno.land/r/sys/users -func AdminEnable -gas-fee 100000ugnot -gas-wanted 1000000 -broadcast -chainid tendermint_test admin +# Enable `sys/names` +# admin call -> sys/names.Enable +gnokey maketx call -pkgpath gno.land/r/sys/names -func Enable -gas-fee 100000ugnot -gas-wanted 1000000 -broadcast -chainid tendermint_test admin stdout 'OK!' -# Check that `sys/users` has been enabled -# gui call -> sys/users.IsEnable -gnokey maketx call -pkgpath gno.land/r/sys/users -func IsEnabled -gas-fee 100000ugnot -gas-wanted 200000 -broadcast -chainid tendermint_test gui +# Check that `sys/names` has been enabled +# gui call -> sys/names.IsEnabled +gnokey maketx call -pkgpath gno.land/r/sys/names -func IsEnabled -gas-fee 100000ugnot -gas-wanted 200000 -broadcast -chainid tendermint_test gui stdout 'OK!' stdout 'true' # Try to add a pkg an with unregistered user # gui addpkg -> gno.land/r//one -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui -stderr 'unauthorized user' +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/one -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test gui +stderr 'is not authorized to deploy packages to namespace' # Try to add a pkg with an unregistered user, on their own address as namespace # gui addpkg -> gno.land/r//one -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$gui_user_addr/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$gui_user_addr/one -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test gui stdout 'OK!' ## Test unregistered namespace @@ -56,35 +50,29 @@ stdout 'OK!' # Call addpkg with admin user on gui namespace # admin addpkg -> gno.land/r/guiland/one # This is expected to fail at the transaction simulation stage, which is why we set gas-wanted to 1. -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 1 -broadcast -chainid=tendermint_test admin -stderr 'unauthorized user' +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test admin +stderr 'is not authorized to deploy packages to namespace' ## Test registered namespace -# Test admin invites gui -# admin call -> demo/users.Invite -gnokey maketx call -pkgpath gno.land/r/demo/users -func Invite -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $gui_user_addr admin -stdout 'OK!' - # test gui register namespace -# gui call -> demo/users.Register -gnokey maketx call -pkgpath gno.land/r/demo/users -func Register -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $admin_user_addr -args 'guiland' -args 'im gui' gui +# gui call -> gnoland/users/v1.Register +gnokey maketx call -pkgpath gno.land/r/gnoland/users/v1 -func Register -send "1000000ugnot" -gas-fee 1000000ugnot -gas-wanted 12000000 -broadcast -chainid=tendermint_test -args 'guigui123' gui stdout 'OK!' -# Test gui publishing on guiland/one -# gui addpkg -> gno.land/r/guiland/one -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 1800000 -broadcast -chainid=tendermint_test gui +# Test gui publishing on guigui123/one +# gui addpkg -> gno.land/r/guigui123/one +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guigui123/one -gas-fee 1000000ugnot -gas-wanted 2700000 -broadcast -chainid=tendermint_test gui stdout 'OK!' -# Test admin publishing on guiland/two -# admin addpkg -> gno.land/r/guiland/two -# This is expected to fail at the transaction simulation stage, which is why we set gas-wanted to 1. -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/two -gas-fee 1000000ugnot -gas-wanted 1 -broadcast -chainid=tendermint_test admin -stderr 'unauthorized user' +# Test admin publishing on guigui123/two +# admin addpkg -> gno.land/r/guigui123/two +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guigui123/two -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test admin +stderr 'is not authorized to deploy packages to namespace' -- one.gno -- package one func Render(path string) string { - return "# Hello One" -} + return "# Hello One" +} \ No newline at end of file diff --git a/gno.land/pkg/integration/testdata/adduserfrom.txtar b/gno.land/pkg/integration/testdata/adduserfrom.txtar index 8bbfaa738fd..a4b71479cae 100644 --- a/gno.land/pkg/integration/testdata/adduserfrom.txtar +++ b/gno.land/pkg/integration/testdata/adduserfrom.txtar @@ -27,7 +27,7 @@ stdout ' "BaseAccount": {' stdout ' "address": "g1mtmrdmqfu0aryqfl4aw65n35haw2wdjkh5p4cp",' stdout ' "coins": "10000000ugnot",' stdout ' "public_key": null,' -stdout ' "account_number": "59",' +stdout ' "account_number": "60",' stdout ' "sequence": "0"' stdout ' }' stdout '}' diff --git a/gno.land/pkg/integration/testdata/assertorigincall.txtar b/gno.land/pkg/integration/testdata/assertorigincall.txtar index 2c5da25c0aa..f6893bd5ca6 100644 --- a/gno.land/pkg/integration/testdata/assertorigincall.txtar +++ b/gno.land/pkg/integration/testdata/assertorigincall.txtar @@ -35,68 +35,68 @@ gnoland start # Test cases ## 1. MsgCall -> myrlm.A: PANIC -! gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 1 -broadcast -chainid tendermint_test test1 +! gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stderr 'invalid non-origin call' ## 2. MsgCall -> myrlm.B: PASS -gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-wanted 150000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stdout 'OK!' ## 3. MsgCall -> myrlm.C: PASS -gnokey maketx call -pkgpath gno.land/r/myrlm -func C -gas-fee 100000ugnot -gas-wanted 1500000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/myrlm -func C -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stdout 'OK!' ## 4. MsgCall -> r/foo.A -> myrlm.A: PANIC -! gnokey maketx call -pkgpath gno.land/r/foo -func A -gas-fee 100000ugnot -gas-wanted 1 -broadcast -chainid tendermint_test test1 +! gnokey maketx call -pkgpath gno.land/r/foo -func A -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stderr 'invalid non-origin call' ## 5. MsgCall -> r/foo.B -> myrlm.B: PASS -gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wanted 200000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stdout 'OK!' ## 6. MsgCall -> r/foo.C -> myrlm.C: PANIC -! gnokey maketx call -pkgpath gno.land/r/foo -func C -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 +! gnokey maketx call -pkgpath gno.land/r/foo -func C -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 stderr 'invalid non-origin call' ## remove due to update to maketx call can only call realm (case 7,8,9) ## 7. MsgCall -> p/demo/bar.A: PANIC -## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 5000000 -broadcast -chainid tendermint_test test1 +## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 ## stderr 'invalid non-origin call' ## 8. MsgCall -> p/demo/bar.B: PASS -## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 5000000 -broadcast -chainid tendermint_test test1 +## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 ## stdout 'OK!' ## 9. MsgCall -> p/demo/bar.C: PANIC -## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func C -gas-fee 100000ugnot -gas-wanted 5000000 -broadcast -chainid tendermint_test test1 +## ! gnokey maketx call -pkgpath gno.land/p/demo/bar -func C -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 ## stderr 'invalid non-origin call' ## 10. MsgRun -> run.main -> myrlm.A: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmA.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmA.gno stderr 'invalid non-origin call' ## 11. MsgRun -> run.main -> myrlm.B: PASS -gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmB.gno +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmB.gno stdout 'OK!' ## 12. MsgRun -> run.main -> myrlm.C: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmC.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmC.gno stderr 'invalid non-origin call' ## 13. MsgRun -> run.main -> foo.A: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/fooA.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/fooA.gno stderr 'invalid non-origin call' ## 14. MsgRun -> run.main -> foo.B: PASS -gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno +gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno stdout 'OK!' ## 15. MsgRun -> run.main -> foo.C: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/fooC.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/fooC.gno stderr 'invalid non-origin call' ## 16. MsgRun -> run.main -> bar.A: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno stderr 'invalid non-origin call' ## 17. MsgRun -> run.main -> bar.B: PASS @@ -104,16 +104,16 @@ gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid stdout 'OK!' ## 18. MsgRun -> run.main -> bar.C: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 5500000 -broadcast -chainid tendermint_test test1 $WORK/run/barC.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/barC.gno stderr 'invalid non-origin call' ## remove testcase 19 due to maketx call forced to call a realm ## 19. MsgCall -> std.AssertOriginCall: pass -## gnokey maketx call -pkgpath std -func AssertOriginCall -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 +## gnokey maketx call -pkgpath std -func AssertOriginCall -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 ## stdout 'OK!' ## 20. MsgRun -> std.AssertOriginCall: PANIC -! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno +! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 10_000_000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno stderr 'invalid non-origin call' diff --git a/gno.land/pkg/integration/testdata/atomicswap.txtar b/gno.land/pkg/integration/testdata/atomicswap.txtar new file mode 100644 index 00000000000..6d0f89a6dc8 --- /dev/null +++ b/gno.land/pkg/integration/testdata/atomicswap.txtar @@ -0,0 +1,41 @@ +loadpkg gno.land/r/demo/atomicswap +# XXX: would be nice to specify an artibrary initial balance as an "adduser" argument. cc @gfanton +adduser test2 +adduser test3 + +gnoland start + +gnokey maketx send -send 1000000000ugnot -to $test2_user_addr -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 +gnokey maketx send -send 1000000000ugnot -to $test3_user_addr -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid tendermint_test test1 + +gnokey query auth/accounts/$test2_user_addr +stdout 'coins.*:.*1010000000ugnot' + +gnokey query auth/accounts/$test3_user_addr +stdout 'coins.*:.*1010000000ugnot' + +# To generate the hash for "secret", use a hashing tool or library of your choice. For example: +# In Unix-based systems, you can use: +# echo -n "secret" | sha256sum +# This will produce a hashlock string like "2bb808d537b1da3e38bd30361aa85586dbbeacdd7126fef6a25ef97b5f27a25b". +# Replace the hashlock argument in the command below with the generated hash. +gnokey maketx call -pkgpath gno.land/r/demo/atomicswap -func NewCoinSwap -gas-fee 1000000ugnot -send 12345ugnot -gas-wanted 10000000 -args $test3_user_addr -args '2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b' -broadcast -chainid=tendermint_test test2 +stdout '(1 int)' +stdout ".*$test2_user_addr.*$test3_user_addr.*12345ugnot.*" +stdout 'OK!' + +gnokey maketx call -pkgpath gno.land/r/demo/atomicswap -func Render -gas-fee 1000000ugnot -gas-wanted 10000000 -args '' -broadcast -chainid=tendermint_test test2 +stdout 'OK!' + +gnokey query auth/accounts/$test2_user_addr +stdout 'coins.*:.*1007987655ugnot' +gnokey query auth/accounts/$test3_user_addr +stdout 'coins.*:.*1010000000ugnot' + +gnokey maketx call -pkgpath gno.land/r/demo/atomicswap -func Claim -gas-fee 1ugnot -gas-wanted 10000000 -args '1' -args 'secret' -broadcast -chainid=tendermint_test test3 +stdout 'OK!' + +gnokey query auth/accounts/$test2_user_addr +stdout 'coins.*:.*1007987655ugnot' +gnokey query auth/accounts/$test3_user_addr +stdout 'coins.*:.*1010012344ugnot' diff --git a/gno.land/pkg/integration/testdata/err_metadata.txtar b/gno.land/pkg/integration/testdata/err_metadata.txtar new file mode 100644 index 00000000000..06ebec156b4 --- /dev/null +++ b/gno.land/pkg/integration/testdata/err_metadata.txtar @@ -0,0 +1,26 @@ +# ensure users get proper out of gas errors when they add packages + +# start a new node +gnoland start + +! gnokey maketx addpkg -pkgdir $WORK/invalid -pkgpath gno.land/r/invalid -gas-fee 1000000ugnot -gas-wanted 60000 -broadcast -chainid=tendermint_test test1 + +stdout 'TX HASH:' +stdout 'INFO:.*vm.version=develop' +stderr '--= Error =--' +stderr 'Data: invalid gno package; type check errors:' +stderr 'gno.land/r/invalid/invalid.gno:.*:.*: expected operand, found .EOF.' +stderr 'Msg Traces:' +stderr ' 0 .*gno/tm2/pkg/errors/errors.go:.* - deliver transaction failed: log:msg:0,success:false,log:--= Error =--' +stderr 'Data: vm.TypeCheckError{abciError:vm.abciError{}, Errors:..string{"gno.land/r/invalid/invalid.gno:.*:.*: expected operand, found .EOF."}}' +stderr 'Msg Traces:' +stderr 'Stack Trace:' +stderr ' 0 .*gno.land/pkg/sdk/vm/errors.go:.*' +#... +stderr '--= /Error =--' +stderr ',events:..' +stderr '--= /Error =--' + +-- invalid/invalid.gno -- +package invalid +var Foo = diff --git a/gno.land/pkg/integration/testdata/genesis_params.txtar b/gno.land/pkg/integration/testdata/genesis_params.txtar index d09ededf78a..de6f0e38a25 100644 --- a/gno.land/pkg/integration/testdata/genesis_params.txtar +++ b/gno.land/pkg/integration/testdata/genesis_params.txtar @@ -7,22 +7,21 @@ gnoland start # default initialization of "gnoland" provides the expected default values. # Verify the default chain domain parameter for Gno.land -gnokey query params/vm/gno.land/r/sys/params.vm.chain_domain.string +gnokey query params/vm:p:chain_domain stdout 'data: "gno.land"$' # Test custom parameters to confirm they return the expected values and types. -gnokey query params/vm/gno.land/r/sys/params.test.foo.string +gnokey query params/vm:gno.land/r/sys/testrealm:foo_string stdout 'data: "bar"$' -gnokey query params/vm/gno.land/r/sys/params.test.foo.int64 -stdout 'data: "-1337"' +gnokey query params/vm:gno.land/r/sys/testrealm:foo_int64 +stdout 'data: "-1337"$' -gnokey query params/vm/gno.land/r/sys/params.test.foo.uint64 -stdout 'data: "42"' +gnokey query params/vm:gno.land/r/sys/testrealm:foo_bool +stdout 'data: true$' -gnokey query params/vm/gno.land/r/sys/params.test.foo.bool -stdout 'data: true' +gnokey query params/vm:gno.land/r/sys/testrealm:foo_strings +stdout 'data: \["some","strings"\]' -# TODO: Consider adding a test case for a byte array parameter diff --git a/gno.land/pkg/integration/testdata/ghverify.txtar b/gno.land/pkg/integration/testdata/ghverify.txtar index 0f2d21f6bd5..a5df5e0ca29 100644 --- a/gno.land/pkg/integration/testdata/ghverify.txtar +++ b/gno.land/pkg/integration/testdata/ghverify.txtar @@ -20,8 +20,7 @@ gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GetAddressByHandle stdout "" # fail on ingestion with a bad task ID -# This is expected to fail at the transaction simulation stage, which is why we set gas-wanted to 1. -! gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'ingest,a' -gas-fee 1000000ugnot -gas-wanted 1 -broadcast -chainid=tendermint_test test1 +! gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'ingest,a' -gas-fee 1000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 stderr 'invalid ingest id: a' # the agent publishes their response to the task and the verification is complete diff --git a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar index 31b2249f8bb..438c452295b 100644 --- a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar +++ b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar @@ -67,6 +67,10 @@ stdout '"sequence": "5"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' +# simulate should panic if gas-wanted is beyond the max block gas. +! gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args Paul -gas-wanted 100_000_000_000_000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 +stderr 'invalid gas wanted' + -- test/test.gno -- package test diff --git a/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar b/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar index 838db121442..c4d0e6bcdf8 100644 --- a/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar +++ b/gno.land/pkg/integration/testdata/gnoweb_airgapped.txtar @@ -19,7 +19,7 @@ stdout ' "BaseAccount": {' stdout ' "address": "g1meuazsmy8ztaz2xpuyraqq4axy6s00ycl07zva",' stdout ' "coins": "[0-9]*ugnot",' # dynamic stdout ' "public_key": null,' -stdout ' "account_number": "57",' +stdout ' "account_number": "58",' stdout ' "sequence": "0"' stdout ' }' stdout '}' @@ -30,7 +30,7 @@ gnokey maketx call -pkgpath "gno.land/r/demo/echo" -func "Render" -gas-fee 10000 cp stdout call.tx # Sign -gnokey sign -tx-path $WORK/call.tx -chainid "tendermint_test" -account-number 57 -account-sequence 0 user1 +gnokey sign -tx-path $WORK/call.tx -chainid "tendermint_test" -account-number 58 -account-sequence 0 user1 cmpenv stdout sign.stdout.golden gnokey broadcast $WORK/call.tx diff --git a/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar b/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar index 0068384903e..d7c3a7d68cd 100644 --- a/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar +++ b/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar @@ -8,6 +8,6 @@ gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Faucet -gas-fee 10000000 stdout 'OK!' # execute Transfer for invalid address -# This is expected to fail at the transaction simulation stage, which is why we set gas-wanted to 1. -! gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Transfer -args g1ubwj0apf60hd90txhnh855fkac34rxlsvua0aa -args 1 -gas-fee 1000000ugnot -gas-wanted 1 -broadcast -chainid=tendermint_test test1 +# This is expected to fail at the transaction simulation stage. +! gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Transfer -args g1ubwj0apf60hd90txhnh855fkac34rxlsvua0aa -args 1 -gas-fee 1000000ugnot -gas-wanted 10_000_000 -simulate only -broadcast -chainid=tendermint_test test1 stderr '"gnokey" error: --= Error =--\nData: invalid address' diff --git a/gno.land/pkg/integration/testdata/grc20_registry.txtar b/gno.land/pkg/integration/testdata/grc20_registry.txtar index 4377e10a575..ecfab2ea651 100644 --- a/gno.land/pkg/integration/testdata/grc20_registry.txtar +++ b/gno.land/pkg/integration/testdata/grc20_registry.txtar @@ -34,7 +34,7 @@ func TransferByName(name string, to string, amount uint64) string { if pair.name != name { continue } - if std.CurrentRealm().Addr().String() != pair.cb(std.Address(to), amount) { + if std.CurrentRealm().Address().String() != pair.cb(std.Address(to), amount) { return "invalid address, ownership issue :(" } return "same address, success!" @@ -58,6 +58,6 @@ package foo20 import "std" func Transfer(to std.Address, amount uint64) string { - println("transfer from=" + std.PrevRealm().Addr().String() + " to=" + to.String() + " some-amount") - return std.PrevRealm().Addr().String() + println("transfer from=" + std.PreviousRealm().Address().String() + " to=" + to.String() + " some-amount") + return std.PreviousRealm().Address().String() } diff --git a/gno.land/pkg/integration/testdata/grc721_emit.txtar b/gno.land/pkg/integration/testdata/grc721_emit.txtar index 45101b74634..a0196319fcf 100644 --- a/gno.land/pkg/integration/testdata/grc721_emit.txtar +++ b/gno.land/pkg/integration/testdata/grc721_emit.txtar @@ -1,6 +1,5 @@ # Test for https://github.com/gnolang/gno/pull/3102 loadpkg gno.land/p/demo/grc/grc721 -loadpkg gno.land/r/demo/users loadpkg gno.land/r/foo721 $WORK/foo721 gnoland start @@ -33,9 +32,6 @@ import ( "std" "gno.land/p/demo/grc/grc721" - "gno.land/r/demo/users" - - pusers "gno.land/p/demo/users" ) var ( @@ -45,22 +41,22 @@ var ( // Setters -func Approve(user pusers.AddressOrName, tid grc721.TokenID) { - err := foo.Approve(users.Resolve(user), tid) +func Approve(user std.Address, tid grc721.TokenID) { + err := foo.Approve(user, tid) if err != nil { panic(err) } } -func SetApprovalForAll(user pusers.AddressOrName, approved bool) { - err := foo.SetApprovalForAll(users.Resolve(user), approved) +func SetApprovalForAll(user std.Address, approved bool) { + err := foo.SetApprovalForAll(user, approved) if err != nil { panic(err) } } -func TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) { - err := foo.TransferFrom(users.Resolve(from), users.Resolve(to), tid) +func TransferFrom(from, to std.Address, tid grc721.TokenID) { + err := foo.TransferFrom(from, to, tid) if err != nil { panic(err) } @@ -68,17 +64,17 @@ func TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) { // Admin -func Mint(to pusers.AddressOrName, tid grc721.TokenID) { - caller := std.PrevRealm().Addr() +func Mint(to std.Address, tid grc721.TokenID) { + caller := std.PreviousRealm().Address() assertIsAdmin(caller) - err := foo.Mint(users.Resolve(to), tid) + err := foo.Mint(to, tid) if err != nil { panic(err) } } func Burn(tid grc721.TokenID) { - caller := std.PrevRealm().Addr() + caller := std.PreviousRealm().Address() assertIsAdmin(caller) err := foo.Burn(tid) if err != nil { @@ -92,4 +88,4 @@ func assertIsAdmin(address std.Address) { if address != admin { panic("restricted access") } -} +} \ No newline at end of file diff --git a/gno.land/pkg/integration/testdata/improved-coins.txtar b/gno.land/pkg/integration/testdata/improved_coins.txtar similarity index 100% rename from gno.land/pkg/integration/testdata/improved-coins.txtar rename to gno.land/pkg/integration/testdata/improved_coins.txtar diff --git a/gno.land/pkg/integration/testdata/infinite_loop.txtar b/gno.land/pkg/integration/testdata/infinite_loop.txtar new file mode 100644 index 00000000000..30a32bdc588 --- /dev/null +++ b/gno.land/pkg/integration/testdata/infinite_loop.txtar @@ -0,0 +1,78 @@ +# regression test for https://github.com/gnolang/gno/issues/3612 +# infinite loops should panic in all of simulate, query, render, maketx run and addpkg. + +gnoland start + +# addpkg + -simulate skip +! gnokey maketx addpkg -pkgdir $WORK/r1 -pkgpath gno.land/r/demo/r1 -simulate skip -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 +! stdout OK! +stderr 'out of gas.* location: CPUCycles' + +# addpkg + -simulate only +! gnokey maketx addpkg -pkgdir $WORK/r1 -pkgpath gno.land/r/demo/r1 -simulate only -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 +! stdout OK! +stderr 'out of gas.* location: CPUCycles' + +# run + -simulate skip +! gnokey maketx run -simulate skip -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 $WORK/run.gno +! stdout OK! +stderr 'out of gas.* location: CPUCycles' + +# run + -simulate only +! gnokey maketx run -simulate only -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 $WORK/run.gno +! stdout OK! +stderr 'out of gas.* location: CPUCycles' + +# maketx addpkg on r2 (successful) +gnokey maketx addpkg -pkgdir $WORK/r2 -pkgpath gno.land/r/demo/r2 -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +# qeval on the render function +! gnokey query vm/qeval --data "gno.land/r/demo/r2.Render(\"helloworld\")" +stderr 'out of gas.* location: CPUCycles' + +# qrender function +! gnokey query vm/qrender --data 'gno.land/r/demo/r2:noice' +stderr 'out of gas.* location: CPUCycles' + +# call on the render function +! gnokey maketx call -pkgpath gno.land/r/demo/r2 -func Render -args '' -simulate skip -gas-fee 100000ugnot -gas-wanted 1000000 -broadcast -chainid tendermint_test test1 +stderr 'out of gas.* location: CPUCycles' + +# simulated call on the render function +! gnokey maketx call -pkgpath gno.land/r/demo/r2 -func Render -args '' -simulate only -gas-fee 100000ugnot -gas-wanted 1000000 -broadcast -chainid tendermint_test test1 +stderr 'out of gas.* location: CPUCycles' + +-- run.gno -- +package main + +func main() { + for {} + + println("hey") +} + +-- r1/gno.mod -- +module gno.land/r/demo/r1 + + +-- r1/realm.gno -- +package f1 + +func init() { + for {} +} + +func main() {} + +-- r2/gno.mod -- +module gno.land/r/demo/r2 + + +-- r2/realm.gno -- +package r2 + +func Render(s string) string { + for {} + return "hello world!" +} diff --git a/gno.land/pkg/integration/testdata/initctx.txtar b/gno.land/pkg/integration/testdata/initctx.txtar index 9210268e66f..82e27dba642 100644 --- a/gno.land/pkg/integration/testdata/initctx.txtar +++ b/gno.land/pkg/integration/testdata/initctx.txtar @@ -18,8 +18,8 @@ var orig = std.Address("orig") var prev = std.Address("prev") func init() { - orig = std.GetOrigCaller() - prev = std.PrevRealm().Addr() + orig = std.OriginCaller() + prev = std.PreviousRealm().Address() } func Render(addr string) string { diff --git a/gno.land/pkg/integration/testdata/issue_1786.txtar b/gno.land/pkg/integration/testdata/issue_1786.txtar index 1cbaf2c6643..2aa5b2ef88a 100644 --- a/gno.land/pkg/integration/testdata/issue_1786.txtar +++ b/gno.land/pkg/integration/testdata/issue_1786.txtar @@ -46,11 +46,10 @@ import ( "std" "gno.land/r/demo/wugnot" - pusers "gno.land/p/demo/users" ) func ProxyWrap() { - sent := std.GetOrigSend() + sent := std.OriginSend() ugnotSent := uint64(sent.AmountOf("ugnot")) if ugnotSent == 0 { @@ -59,12 +58,12 @@ func ProxyWrap() { // WRAP IT wugnotAddr := std.DerivePkgAddr("gno.land/r/demo/wugnot") - banker := std.GetBanker(std.BankerTypeRealmSend) - banker.SendCoins(std.CurrentRealm().Addr(), wugnotAddr, std.Coins{{"ugnot", int64(ugnotSent)}}) + banker := std.NewBanker(std.BankerTypeRealmSend) + banker.SendCoins(std.CurrentRealm().Address(), wugnotAddr, std.Coins{{"ugnot", int64(ugnotSent)}}) wugnot.Deposit() // `proxywugnot` has ugnot // SEND WUGNOT: PROXY_WUGNOT -> USER - wugnot.Transfer(pusers.AddressOrName(std.GetOrigCaller()), ugnotSent) + wugnot.Transfer(std.OriginCaller(), ugnotSent) } func ProxyUnwrap(wugnotAmount uint64) { @@ -73,12 +72,13 @@ func ProxyUnwrap(wugnotAmount uint64) { } // SEND WUGNOT: USER -> PROXY_WUGNOT - wugnot.TransferFrom(pusers.AddressOrName(std.GetOrigCaller()), pusers.AddressOrName(std.CurrentRealm().Addr()), wugnotAmount) + wugnot.TransferFrom(std.OriginCaller(), std.CurrentRealm().Address(), wugnotAmount) // UNWRAP IT wugnot.Withdraw(wugnotAmount) // SEND GNOT: PROXY_WUGNOT -> USER - banker := std.GetBanker(std.BankerTypeRealmSend) - banker.SendCoins(std.CurrentRealm().Addr(), std.GetOrigCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) + banker := std.NewBanker(std.BankerTypeRealmSend) + banker.SendCoins(std.CurrentRealm().Address(), std.OriginCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) } + diff --git a/gno.land/pkg/integration/testdata/issue_2283.txtar b/gno.land/pkg/integration/testdata/issue_2283.txtar index 653a4dd79b0..33caadb3cc4 100644 --- a/gno.land/pkg/integration/testdata/issue_2283.txtar +++ b/gno.land/pkg/integration/testdata/issue_2283.txtar @@ -3,8 +3,14 @@ # These are not necessary, but they "alleviate" add_feeds.tx from the # responsibility of loading standard libraries, thus not making it exceed # the --gas-wanted. -loadpkg gno.land/r/demo/users -loadpkg gno.land/r/demo/boards +loadpkg gno.land/p/demo/avl +loadpkg gno.land/p/demo/avl/pager +loadpkg gno.land/p/demo/avlhelpers +loadpkg gno.land/p/moul/txlink + +loadpkg gno.land/p/demo/users $WORK/pusers +loadpkg gno.land/r/demo/users $WORK/users +loadpkg gno.land/r/demo/boards $WORK/boards loadpkg gno.land/r/demo/imports $WORK/imports gnoland start @@ -19,7 +25,7 @@ stdout OK! package imports import ( - _ "encoding/binary" + _ "encoding/binary" ) -- add_feeds.tx -- @@ -46,7 +52,7 @@ import ( }, { "Name": "feeds_test.gno", - "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.GetCallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOrigCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOrigCaller(tipper)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOrigCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOrigCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOrigCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOrigCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOrigCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOrigCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.CallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOrigCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOrigCaller(tipper)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOrigCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOrigCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOrigCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOrigCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOrigCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOrigCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" }, { "Name": "flags.gno", @@ -66,11 +72,11 @@ import ( }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.GetOrigSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PrevRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PrevRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PrevRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PrevRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PrevRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PrevRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PreviousRealm().Address()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PreviousRealm().Address()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PreviousRealm().Address() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PreviousRealm().Address()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PreviousRealm().Address()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PreviousRealm().Address() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Address()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" }, { "Name": "render.gno", @@ -101,11 +107,1240 @@ import ( package bye import ( - "encoding/base64" + "encoding/base64" ) func init() { - val, _ := base64.StdEncoding.DecodeString("heyhey") - println(val) - base64.StdEncoding.EncodeToString([]byte(val)) + val, _ := base64.StdEncoding.DecodeString("heyhey") + println(val) + base64.StdEncoding.EncodeToString([]byte(val)) +} + +-- pusers/gno.mod -- +module gno.land/p/demo/users + +-- pusers/users.gno -- +package users + +import ( + "std" + "strconv" +) + +//---------------------------------------- +// Types + +type User struct { + Address std.Address + Name string + Profile string + Number int + Invites int + Inviter std.Address +} + +func (u *User) Render() string { + str := "## user " + u.Name + "\n" + + "\n" + + " * address = " + string(u.Address) + "\n" + + " * " + strconv.Itoa(u.Invites) + " invites\n" + if u.Inviter != "" { + str = str + " * invited by " + string(u.Inviter) + "\n" + } + str = str + "\n" + + u.Profile + "\n" + return str +} + +type AddressOrName string + +func (aon AddressOrName) IsName() bool { + return aon != "" && aon[0] == '@' +} + +func (aon AddressOrName) GetName() (string, bool) { + if len(aon) >= 2 && aon[0] == '@' { + return string(aon[1:]), true + } + return "", false +} + +-- users/gno.mod -- +module gno.land/r/demo/users + +-- users/users.gno -- +package users + +import ( + "regexp" + "std" + "strconv" + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/demo/avl/pager" + "gno.land/p/demo/avlhelpers" + "gno.land/p/demo/users" +) + +//---------------------------------------- +// State + +var ( + admin std.Address = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul + + restricted avl.Tree // Name -> true - restricted name + name2User avl.Tree // Name -> *users.User + addr2User avl.Tree // std.Address -> *users.User + invites avl.Tree // string(inviter+":"+invited) -> true + counter int // user id counter + minFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register. + maxFeeMult int64 = 10 // maximum multiples of minFee accepted. +) + +//---------------------------------------- +// Top-level functions + +func Register(inviter std.Address, name string, profile string) { + // assert CallTx call. + std.AssertOriginCall() + // assert invited or paid. + caller := std.CallerAt(2) + if caller != std.OriginCaller() { + panic("should not happen") // because std.AssertOrigCall(). + } + + sentCoins := std.OriginSend() + minCoin := std.NewCoin("ugnot", minFee) + + if inviter == "" { + // banker := std.GetBanker(std.BankerTypeOrigSend) + if len(sentCoins) == 1 && sentCoins[0].IsGTE(minCoin) { + if sentCoins[0].Amount > minFee*maxFeeMult { + panic("payment must not be greater than " + strconv.Itoa(int(minFee*maxFeeMult))) + } else { + // ok + } + } else { + panic("payment must not be less than " + strconv.Itoa(int(minFee))) + } + } else { + invitekey := inviter.String() + ":" + caller.String() + _, ok := invites.Get(invitekey) + if !ok { + panic("invalid invitation") + } + invites.Remove(invitekey) + } + + // assert not already registered. + _, ok := name2User.Get(name) + if ok { + panic("name already registered: " + name) + } + _, ok = addr2User.Get(caller.String()) + if ok { + panic("address already registered: " + caller.String()) + } + + isInviterAdmin := inviter == admin + + // check for restricted name + if _, isRestricted := restricted.Get(name); isRestricted { + // only address invite by the admin can register restricted name + if !isInviterAdmin { + panic("restricted name: " + name) + } + + restricted.Remove(name) + } + + // assert name is valid. + // admin inviter can bypass name restriction + if !isInviterAdmin && !reName.MatchString(name) { + panic("invalid name: " + name + " (must be at least 6 characters, lowercase alphanumeric with underscore)") + } + + // remainder of fees go toward invites. + invites := int(0) + if len(sentCoins) == 1 { + if sentCoins[0].Denom == "ugnot" && sentCoins[0].Amount >= minFee { + invites = int(sentCoins[0].Amount / minFee) + if inviter == "" && invites > 0 { + invites -= 1 + } + } + } + // register. + counter++ + user := &users.User{ + Address: caller, + Name: name, + Profile: profile, + Number: counter, + Invites: invites, + Inviter: inviter, + } + name2User.Set(name, user) + addr2User.Set(caller.String(), user) +} + +func Invite(invitee string) { + // assert CallTx call. + std.AssertOriginCall() + // get caller/inviter. + caller := std.CallerAt(2) + if caller != std.OriginCaller() { + panic("should not happen") // because std.AssertOrigCall(). + } + lines := strings.Split(invitee, "\n") + if caller == admin { + // nothing to do, all good + } else { + // ensure has invites. + userI, ok := addr2User.Get(caller.String()) + if !ok { + panic("user unknown") + } + user := userI.(*users.User) + if user.Invites <= 0 { + panic("user has no invite tokens") + } + user.Invites -= len(lines) + if user.Invites < 0 { + panic("user has insufficient invite tokens") + } + } + // for each line... + for _, line := range lines { + if line == "" { + continue // file bodies have a trailing newline. + } else if strings.HasPrefix(line, `//`) { + continue // comment + } + // record invite. + invitekey := string(caller) + ":" + string(line) + invites.Set(invitekey, true) + } +} + +func GrantInvites(invites string) { + // assert CallTx call. + std.AssertOriginCall() + // assert admin. + caller := std.CallerAt(2) + if caller != std.OriginCaller() { + panic("should not happen") // because std.AssertOrigCall(). + } + if caller != admin { + panic("unauthorized") + } + // for each line... + lines := strings.Split(invites, "\n") + for _, line := range lines { + if line == "" { + continue // file bodies have a trailing newline. + } else if strings.HasPrefix(line, `//`) { + continue // comment + } + // parse name and invites. + var name string + var invites int + parts := strings.Split(line, ":") + if len(parts) == 1 { // short for :1. + name = parts[0] + invites = 1 + } else if len(parts) == 2 { + name = parts[0] + invites_, err := strconv.Atoi(parts[1]) + if err != nil { + panic(err) + } + invites = int(invites_) + } else { + panic("should not happen") + } + // give invites. + userI, ok := name2User.Get(name) + if !ok { + // maybe address. + userI, ok = addr2User.Get(name) + if !ok { + panic("invalid user " + name) + } + } + user := userI.(*users.User) + user.Invites += invites + } +} + +// Any leftover fees go toward invitations. +func SetMinFee(newMinFee int64) { + // assert CallTx call. + std.AssertOriginCall() + // assert admin caller. + caller := std.CallerAt(2) + if caller != admin { + panic("unauthorized") + } + // update global variables. + minFee = newMinFee +} + +// This helps prevent fat finger accidents. +func SetMaxFeeMultiple(newMaxFeeMult int64) { + // assert CallTx call. + std.AssertOriginCall() + // assert admin caller. + caller := std.CallerAt(2) + if caller != admin { + panic("unauthorized") + } + // update global variables. + maxFeeMult = newMaxFeeMult +} + +//---------------------------------------- +// Exposed public functions + +func GetUserByName(name string) *users.User { + userI, ok := name2User.Get(name) + if !ok { + return nil + } + return userI.(*users.User) +} + +func GetUserByAddress(addr std.Address) *users.User { + userI, ok := addr2User.Get(addr.String()) + if !ok { + return nil + } + return userI.(*users.User) +} + +// unlike GetUserByName, input must be "@" prefixed for names. +func GetUserByAddressOrName(input users.AddressOrName) *users.User { + name, isName := input.GetName() + if isName { + return GetUserByName(name) + } + return GetUserByAddress(std.Address(input)) +} + +// Get a list of user names starting from the given prefix. Limit the +// number of results to maxResults. (This can be used for a name search tool.) +func ListUsersByPrefix(prefix string, maxResults int) []string { + return avlhelpers.ListByteStringKeysByPrefix(&name2User, prefix, maxResults) +} + +func Resolve(input users.AddressOrName) std.Address { + name, isName := input.GetName() + if !isName { + return std.Address(input) // TODO check validity + } + + user := GetUserByName(name) + return user.Address +} + +// Add restricted name to the list +func AdminAddRestrictedName(name string) { + // assert CallTx call. + std.AssertOriginCall() + // get caller + caller := std.OriginCaller() + // assert admin + if caller != admin { + panic("unauthorized") + } + + if user := GetUserByName(name); user != nil { + panic("already registered name") + } + + // register restricted name + + restricted.Set(name, true) +} + +//---------------------------------------- +// Constants + +// NOTE: name length must be clearly distinguishable from a bech32 address. +var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`) + +//---------------------------------------- +// Render main page + +func Render(fullPath string) string { + path, _ := splitPathAndQuery(fullPath) + if path == "" { + return renderHome(fullPath) + } else if len(path) >= 38 { // 39? 40? + if path[:2] != "g1" { + return "invalid address " + path + } + user := GetUserByAddress(std.Address(path)) + if user == nil { + // TODO: display basic information about account. + return "unknown address " + path + } + return user.Render() + } else { + user := GetUserByName(path) + if user == nil { + return "unknown username " + path + } + return user.Render() + } +} + +func renderHome(path string) string { + doc := "" + + page := pager.NewPager(&name2User, 50, false).MustGetPageByPath(path) + + for _, item := range page.Items { + user := item.Value.(*users.User) + doc += " * [" + user.Name + "](/r/demo/users:" + user.Name + ")\n" + } + doc += "\n" + doc += page.Picker() + return doc +} + +func splitPathAndQuery(fullPath string) (string, string) { + parts := strings.SplitN(fullPath, "?", 2) + path := parts[0] + queryString := "" + if len(parts) > 1 { + queryString = "?" + parts[1] + } + return path, queryString +} + +// pre-restricted names +var preRestrictedNames = []string{ + "bitcoin", "cosmos", "newtendermint", "ethereum", +} + +// pre-registered users +var preRegisteredUsers = []struct { + Name string + Address std.Address +}{ + // system name + {"archives", "g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k"}, // -> @r_archives + {"demo", "g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n"}, // -> @r_demo + {"gno", "g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a"}, // -> @r_gno + {"gnoland", "g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7"}, // -> @r_gnoland + {"gnolang", "g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd"}, // -> @r_gnolang + {"gov", "g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da"}, // -> @r_gov + {"nt", "g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l"}, // -> @r_nt + {"sys", "g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l"}, // -> @r_sys + {"x", "g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz"}, // -> @r_x + + // test1 user + {"test1", "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"}, // -> @test1 +} + +func init() { + // add pre-registered users + for _, res := range preRegisteredUsers { + // assert not already registered. + _, ok := name2User.Get(res.Name) + if ok { + panic("name already registered") + } + + _, ok = addr2User.Get(res.Address.String()) + if ok { + panic("address already registered") + } + + counter++ + user := &users.User{ + Address: res.Address, + Name: res.Name, + Profile: "", + Number: counter, + Invites: int(0), + Inviter: admin, + } + name2User.Set(res.Name, user) + addr2User.Set(res.Address.String(), user) + } + + // add pre-restricted names + for _, name := range preRestrictedNames { + if _, ok := name2User.Get(name); ok { + panic("name already registered") + } + + restricted.Set(name, true) + } +} + +-- boards/gno.mod -- +module gno.land/r/demo/boards + +-- boards/boards.gno -- +package boards + +import ( + "std" + "strconv" + "regexp" + "time" + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/moul/txlink" + + "gno.land/r/demo/users" + +) + +//---------------------------------------- +// Board + +type BoardID uint64 + +func (bid BoardID) String() string { + return strconv.Itoa(int(bid)) +} + +type Board struct { + id BoardID // only set for public boards. + url string + name string + creator std.Address + threads avl.Tree // Post.id -> *Post + postsCtr uint64 // increments Post.id + createdAt time.Time + deleted avl.Tree // TODO reserved for fast-delete. +} + +func newBoard(id BoardID, url string, name string, creator std.Address) *Board { + if !reName.MatchString(name) { + panic("invalid name: " + name) + } + exists := gBoardsByName.Has(name) + if exists { + panic("board already exists") + } + return &Board{ + id: id, + url: url, + name: name, + creator: creator, + threads: avl.Tree{}, + createdAt: time.Now(), + deleted: avl.Tree{}, + } +} + +/* TODO support this once we figure out how to ensure URL correctness. +// A private board is not tracked by gBoards*, +// but must be persisted by the caller's realm. +// Private boards have 0 id and does not ping +// back the remote board on reposts. +func NewPrivateBoard(url string, name string, creator std.Address) *Board { + return newBoard(0, url, name, creator) +} +*/ + +func (board *Board) IsPrivate() bool { + return board.id == 0 +} + +func (board *Board) GetThread(pid PostID) *Post { + pidkey := postIDKey(pid) + postI, exists := board.threads.Get(pidkey) + if !exists { + return nil + } + return postI.(*Post) +} + +func (board *Board) AddThread(creator std.Address, title string, body string) *Post { + pid := board.incGetPostID() + pidkey := postIDKey(pid) + thread := newPost(board, pid, creator, title, body, pid, 0, 0) + board.threads.Set(pidkey, thread) + return thread +} + +// NOTE: this can be potentially very expensive for threads with many replies. +// TODO: implement optional fast-delete where thread is simply moved. +func (board *Board) DeleteThread(pid PostID) { + pidkey := postIDKey(pid) + _, removed := board.threads.Remove(pidkey) + if !removed { + panic("thread does not exist with id " + pid.String()) + } +} + +func (board *Board) HasPermission(addr std.Address, perm Permission) bool { + if board.creator == addr { + switch perm { + case EditPermission: + return true + case DeletePermission: + return true + default: + return false + } + } + return false +} + +// Renders the board for display suitable as plaintext in +// console. This is suitable for demonstration or tests, +// but not for prod. +func (board *Board) RenderBoard() string { + str := "" + str += "\\[[post](" + board.GetPostFormURL() + ")]\n\n" + if board.threads.Size() > 0 { + board.threads.Iterate("", "", func(key string, value interface{}) bool { + if str != "" { + str += "----------------------------------------\n" + } + str += value.(*Post).RenderSummary() + "\n" + return false + }) + } + return str +} + +func (board *Board) incGetPostID() PostID { + board.postsCtr++ + return PostID(board.postsCtr) +} + +func (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string { + if replyID == 0 { + return board.url + "/" + threadID.String() + } else { + return board.url + "/" + threadID.String() + "/" + replyID.String() + } +} + +func (board *Board) GetPostFormURL() string { + return txlink.Call("CreateThread", "bid", board.id.String()) +} + +var ( + gBoards avl.Tree // id -> *Board + gBoardsCtr int // increments Board.id + gBoardsByName avl.Tree // name -> *Board + gDefaultAnonFee = 100000000 // minimum fee required if anonymous +) + +//---------------------------------------- +// Constants + +var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`) + +//---------------------------------------- +// private utility methods +// XXX ensure these cannot be called from public. + +func getBoard(bid BoardID) *Board { + bidkey := boardIDKey(bid) + board_, exists := gBoards.Get(bidkey) + if !exists { + return nil + } + board := board_.(*Board) + return board +} + +func incGetBoardID() BoardID { + gBoardsCtr++ + return BoardID(gBoardsCtr) +} + +func padLeft(str string, length int) string { + if len(str) >= length { + return str + } else { + return strings.Repeat(" ", length-len(str)) + str + } +} + +func padZero(u64 uint64, length int) string { + str := strconv.Itoa(int(u64)) + if len(str) >= length { + return str + } else { + return strings.Repeat("0", length-len(str)) + str + } +} + +func boardIDKey(bid BoardID) string { + return padZero(uint64(bid), 10) +} + +func postIDKey(pid PostID) string { + return padZero(uint64(pid), 10) +} + +func indentBody(indent string, body string) string { + lines := strings.Split(body, "\n") + res := "" + for i, line := range lines { + if i > 0 { + res += "\n" + } + res += indent + line + } + return res +} + +// NOTE: length must be greater than 3. +func summaryOf(str string, length int) string { + lines := strings.SplitN(str, "\n", 2) + line := lines[0] + if len(line) > length { + line = line[:(length-3)] + "..." + } else if len(lines) > 1 { + // len(line) <= 80 + line = line + "..." + } + return line +} + +func displayAddressMD(addr std.Address) string { + user := users.GetUserByAddress(addr) + if user == nil { + return "[" + addr.String() + "](/r/demo/users:" + addr.String() + ")" + } else { + return "[@" + user.Name + "](/r/demo/users:" + user.Name + ")" + } +} + +func usernameOf(addr std.Address) string { + user := users.GetUserByAddress(addr) + if user == nil { + return "" + } + return user.Name +} + +//---------------------------------------- +// Post + +// NOTE: a PostID is relative to the board. +type PostID uint64 + +func (pid PostID) String() string { + return strconv.Itoa(int(pid)) +} + +// A Post is a "thread" or a "reply" depending on context. +// A thread is a Post of a Board that holds other replies. +type Post struct { + board *Board + id PostID + creator std.Address + title string // optional + body string + replies avl.Tree // Post.id -> *Post + repliesAll avl.Tree // Post.id -> *Post (all replies, for top-level posts) + reposts avl.Tree // Board.id -> Post.id + threadID PostID // original Post.id + parentID PostID // parent Post.id (if reply or repost) + repostBoard BoardID // original Board.id (if repost) + createdAt time.Time + updatedAt time.Time +} + +func newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post { + return &Post{ + board: board, + id: id, + creator: creator, + title: title, + body: body, + replies: avl.Tree{}, + repliesAll: avl.Tree{}, + reposts: avl.Tree{}, + threadID: threadID, + parentID: parentID, + repostBoard: repostBoard, + createdAt: time.Now(), + } +} + +func (post *Post) IsThread() bool { + return post.parentID == 0 +} + +func (post *Post) GetPostID() PostID { + return post.id +} + +func (post *Post) AddReply(creator std.Address, body string) *Post { + board := post.board + pid := board.incGetPostID() + pidkey := postIDKey(pid) + reply := newPost(board, pid, creator, "", body, post.threadID, post.id, 0) + post.replies.Set(pidkey, reply) + if post.threadID == post.id { + post.repliesAll.Set(pidkey, reply) + } else { + thread := board.GetThread(post.threadID) + thread.repliesAll.Set(pidkey, reply) + } + return reply +} + +func (post *Post) Update(title string, body string) { + post.title = title + post.body = body + post.updatedAt = time.Now() +} + +func (thread *Post) GetReply(pid PostID) *Post { + pidkey := postIDKey(pid) + replyI, ok := thread.repliesAll.Get(pidkey) + if !ok { + return nil + } else { + return replyI.(*Post) + } +} + +func (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post { + if !post.IsThread() { + panic("cannot repost non-thread post") + } + pid := dst.incGetPostID() + pidkey := postIDKey(pid) + repost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id) + dst.threads.Set(pidkey, repost) + if !dst.IsPrivate() { + bidkey := boardIDKey(dst.id) + post.reposts.Set(bidkey, pid) + } + return repost +} + +func (thread *Post) DeletePost(pid PostID) { + if thread.id == pid { + panic("should not happen") + } + pidkey := postIDKey(pid) + postI, removed := thread.repliesAll.Remove(pidkey) + if !removed { + panic("post not found in thread") + } + post := postI.(*Post) + if post.parentID != thread.id { + parent := thread.GetReply(post.parentID) + parent.replies.Remove(pidkey) + } else { + thread.replies.Remove(pidkey) + } +} + +func (post *Post) HasPermission(addr std.Address, perm Permission) bool { + if post.creator == addr { + switch perm { + case EditPermission: + return true + case DeletePermission: + return true + default: + return false + } + } + // post notes inherit permissions of the board. + return post.board.HasPermission(addr, perm) +} + +func (post *Post) GetSummary() string { + return summaryOf(post.body, 80) } + +func (post *Post) GetURL() string { + if post.IsThread() { + return post.board.GetURLFromThreadAndReplyID( + post.id, 0) + } else { + return post.board.GetURLFromThreadAndReplyID( + post.threadID, post.id) + } +} + +func (post *Post) GetReplyFormURL() string { + return txlink.Call("CreateReply", + "bid", post.board.id.String(), + "threadid", post.threadID.String(), + "postid", post.id.String(), + ) +} + +func (post *Post) GetRepostFormURL() string { + return txlink.Call("CreateRepost", + "bid", post.board.id.String(), + "postid", post.id.String(), + ) +} + +func (post *Post) GetDeleteFormURL() string { + return txlink.Call("DeletePost", + "bid", post.board.id.String(), + "threadid", post.threadID.String(), + "postid", post.id.String(), + ) +} + +func (post *Post) RenderSummary() string { + if post.repostBoard != 0 { + dstBoard := getBoard(post.repostBoard) + if dstBoard == nil { + panic("repostBoard does not exist") + } + thread := dstBoard.GetThread(PostID(post.parentID)) + if thread == nil { + return "reposted post does not exist" + } + return "Repost: " + post.GetSummary() + "\n" + thread.RenderSummary() + } + str := "" + if post.title != "" { + str += "## [" + summaryOf(post.title, 80) + "](" + post.GetURL() + ")\n" + str += "\n" + } + str += post.GetSummary() + "\n" + str += "\\- " + displayAddressMD(post.creator) + "," + str += " [" + post.createdAt.Format("2006-01-02 3:04pm MST") + "](" + post.GetURL() + ")" + str += " \\[[x](" + post.GetDeleteFormURL() + ")]" + str += " (" + strconv.Itoa(post.replies.Size()) + " replies)" + str += " (" + strconv.Itoa(post.reposts.Size()) + " reposts)" + "\n" + return str +} + +func (post *Post) RenderPost(indent string, levels int) string { + if post == nil { + return "nil post" + } + str := "" + if post.title != "" { + str += indent + "# " + post.title + "\n" + str += indent + "\n" + } + str += indentBody(indent, post.body) + "\n" // TODO: indent body lines. + str += indent + "\\- " + displayAddressMD(post.creator) + ", " + str += "[" + post.createdAt.Format("2006-01-02 3:04pm (MST)") + "](" + post.GetURL() + ")" + str += " \\[[reply](" + post.GetReplyFormURL() + ")]" + if post.IsThread() { + str += " \\[[repost](" + post.GetRepostFormURL() + ")]" + } + str += " \\[[x](" + post.GetDeleteFormURL() + ")]\n" + if levels > 0 { + if post.replies.Size() > 0 { + post.replies.Iterate("", "", func(key string, value interface{}) bool { + str += indent + "\n" + str += value.(*Post).RenderPost(indent+"> ", levels-1) + return false + }) + } + } else { + if post.replies.Size() > 0 { + str += indent + "\n" + str += indent + "_[see all " + strconv.Itoa(post.replies.Size()) + " replies](" + post.GetURL() + ")_\n" + } + } + return str +} + +// render reply and link to context thread +func (post *Post) RenderInner() string { + if post.IsThread() { + panic("unexpected thread") + } + threadID := post.threadID + // replyID := post.id + parentID := post.parentID + str := "" + str += "_[see thread](" + post.board.GetURLFromThreadAndReplyID( + threadID, 0) + ")_\n\n" + thread := post.board.GetThread(post.threadID) + var parent *Post + if thread.id == parentID { + parent = thread + } else { + parent = thread.GetReply(parentID) + } + str += parent.RenderPost("", 0) + str += "\n" + str += post.RenderPost("> ", 5) + return str +} + +//---------------------------------------- +// Public facing functions + +func GetBoardIDFromName(name string) (BoardID, bool) { + boardI, exists := gBoardsByName.Get(name) + if !exists { + return 0, false + } + return boardI.(*Board).id, true +} + +func CreateBoard(name string) BoardID { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + bid := incGetBoardID() + caller := std.OriginCaller() + if usernameOf(caller) == "" { + panic("unauthorized") + } + url := "/r/demo/boards:" + name + board := newBoard(bid, url, name, caller) + bidkey := boardIDKey(bid) + gBoards.Set(bidkey, board) + gBoardsByName.Set(name, board) + return board.id +} + +func checkAnonFee() bool { + sent := std.OriginSend() + anonFeeCoin := std.NewCoin("ugnot", int64(gDefaultAnonFee)) + if len(sent) == 1 && sent[0].IsGTE(anonFeeCoin) { + return true + } + return false +} + +func CreateThread(bid BoardID, title string, body string) PostID { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + caller := std.OriginCaller() + if usernameOf(caller) == "" { + if !checkAnonFee() { + panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") + } + } + board := getBoard(bid) + if board == nil { + panic("board not exist") + } + thread := board.AddThread(caller, title, body) + return thread.id +} + +func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + caller := std.OriginCaller() + if usernameOf(caller) == "" { + if !checkAnonFee() { + panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") + } + } + board := getBoard(bid) + if board == nil { + panic("board not exist") + } + thread := board.GetThread(threadid) + if thread == nil { + panic("thread not exist") + } + if postid == threadid { + reply := thread.AddReply(caller, body) + return reply.id + } else { + post := thread.GetReply(postid) + reply := post.AddReply(caller, body) + return reply.id + } +} + +// If dstBoard is private, does not ping back. +// If board specified by bid is private, panics. +func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + caller := std.OriginCaller() + if usernameOf(caller) == "" { + // TODO: allow with gDefaultAnonFee payment. + if !checkAnonFee() { + panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") + } + } + board := getBoard(bid) + if board == nil { + panic("src board not exist") + } + if board.IsPrivate() { + panic("cannot repost from a private board") + } + dst := getBoard(dstBoardID) + if dst == nil { + panic("dst board not exist") + } + thread := board.GetThread(postid) + if thread == nil { + panic("thread not exist") + } + repost := thread.AddRepostTo(caller, title, body, dst) + return repost.id +} + +func DeletePost(bid BoardID, threadid, postid PostID, reason string) { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + caller := std.OriginCaller() + board := getBoard(bid) + if board == nil { + panic("board not exist") + } + thread := board.GetThread(threadid) + if thread == nil { + panic("thread not exist") + } + if postid == threadid { + // delete thread + if !thread.HasPermission(caller, DeletePermission) { + panic("unauthorized") + } + board.DeleteThread(threadid) + } else { + // delete thread's post + post := thread.GetReply(postid) + if post == nil { + panic("post not exist") + } + if !post.HasPermission(caller, DeletePermission) { + panic("unauthorized") + } + thread.DeletePost(postid) + } +} + +func EditPost(bid BoardID, threadid, postid PostID, title, body string) { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + caller := std.OriginCaller() + board := getBoard(bid) + if board == nil { + panic("board not exist") + } + thread := board.GetThread(threadid) + if thread == nil { + panic("thread not exist") + } + if postid == threadid { + // edit thread + if !thread.HasPermission(caller, EditPermission) { + panic("unauthorized") + } + thread.Update(title, body) + } else { + // edit thread's post + post := thread.GetReply(postid) + if post == nil { + panic("post not exist") + } + if !post.HasPermission(caller, EditPermission) { + panic("unauthorized") + } + post.Update(title, body) + } +} + +//---------------------------------------- +// Render functions + +func RenderBoard(bid BoardID) string { + board := getBoard(bid) + if board == nil { + return "missing board" + } + return board.RenderBoard() +} + +func Render(path string) string { + if path == "" { + str := "These are all the boards of this realm:\n\n" + gBoards.Iterate("", "", func(key string, value interface{}) bool { + board := value.(*Board) + str += " * [" + board.url + "](" + board.url + ")\n" + return false + }) + return str + } + parts := strings.Split(path, "/") + if len(parts) == 1 { + // /r/demo/boards:BOARD_NAME + name := parts[0] + boardI, exists := gBoardsByName.Get(name) + if !exists { + return "board does not exist: " + name + } + return boardI.(*Board).RenderBoard() + } else if len(parts) == 2 { + // /r/demo/boards:BOARD_NAME/THREAD_ID + name := parts[0] + boardI, exists := gBoardsByName.Get(name) + if !exists { + return "board does not exist: " + name + } + pid, err := strconv.Atoi(parts[1]) + if err != nil { + return "invalid thread id: " + parts[1] + } + board := boardI.(*Board) + thread := board.GetThread(PostID(pid)) + if thread == nil { + return "thread does not exist with id: " + parts[1] + } + return thread.RenderPost("", 5) + } else if len(parts) == 3 { + // /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID + name := parts[0] + boardI, exists := gBoardsByName.Get(name) + if !exists { + return "board does not exist: " + name + } + pid, err := strconv.Atoi(parts[1]) + if err != nil { + return "invalid thread id: " + parts[1] + } + board := boardI.(*Board) + thread := board.GetThread(PostID(pid)) + if thread == nil { + return "thread does not exist with id: " + parts[1] + } + rid, err := strconv.Atoi(parts[2]) + if err != nil { + return "invalid reply id: " + parts[2] + } + reply := thread.GetReply(PostID(rid)) + if reply == nil { + return "reply does not exist with id: " + parts[2] + } + return reply.RenderInner() + } else { + return "unrecognized path " + path + } +} + +type Permission string + +const ( + DeletePermission Permission = "role:delete" + EditPermission Permission = "role:edit" +) \ No newline at end of file diff --git a/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar b/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar index 95bd48c0144..3c68d6169af 100644 --- a/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar +++ b/gno.land/pkg/integration/testdata/issue_2283_cacheTypes.txtar @@ -6,8 +6,14 @@ # These are not necessary, but they "alleviate" add_feeds.tx from the # responsibility of loading standard libraries, thus not making it exceed # the --gas-wanted. -loadpkg gno.land/r/demo/users -loadpkg gno.land/r/demo/boards +loadpkg gno.land/p/demo/avl +loadpkg gno.land/p/demo/avl/pager +loadpkg gno.land/p/demo/avlhelpers +loadpkg gno.land/p/moul/txlink + +loadpkg gno.land/p/demo/users $WORK/pusers +loadpkg gno.land/r/demo/users $WORK/users +loadpkg gno.land/r/demo/boards $WORK/boards gnoland start @@ -40,7 +46,7 @@ stdout OK! }, { "Name": "feeds_test.gno", - "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.GetCallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOrigCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOrigCaller(tipper)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOrigCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOrigCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOrigCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOrigCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOrigCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOrigCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/r/demo/boards\"\n\t// Fake previous version for testing\n\tfeedsV7 \"gno.land/r/demo/teritori/social_feeds\"\n\t\"gno.land/r/demo/users\"\n)\n\nvar (\n\trootPostID = PostID(0)\n\tpostID1 = PostID(1)\n\tfeedID1 = FeedID(1)\n\tcat1 = uint64(1)\n\tcat2 = uint64(2)\n\tuser = testutils.TestAddress(\"user\")\n\tfilter_all = []uint64{}\n)\n\nfunc getFeed1() *Feed {\n\treturn mustGetFeed(feedID1)\n}\n\nfunc getPost1() *Post {\n\tfeed1 := getFeed1()\n\tpost1 := feed1.MustGetPost(postID1)\n\treturn post1\n}\n\nfunc testCreateFeed(t *testing.T) {\n\tfeedID := CreateFeed(\"teritori1\")\n\tfeed := mustGetFeed(feedID)\n\n\tif feedID != 1 {\n\t\tt.Fatalf(\"expected feedID: 1, got %q.\", feedID)\n\t}\n\n\tif feed.name != \"teritori1\" {\n\t\tt.Fatalf(\"expected feedName: teritori1, got %q.\", feed.name)\n\t}\n}\n\nfunc testCreatePost(t *testing.T) {\n\tmetadata := `{\"gifs\": [], \"files\": [], \"title\": \"\", \"message\": \"testouille\", \"hashtags\": [], \"mentions\": [], \"createdAt\": \"2023-03-29T12:19:04.858Z\", \"updatedAt\": \"2023-03-29T12:19:04.858Z\"}`\n\tpostID := CreatePost(feedID1, rootPostID, cat1, metadata)\n\tfeed := mustGetFeed(feedID1)\n\tpost := feed.MustGetPost(postID)\n\n\tif postID != 1 {\n\t\tt.Fatalf(\"expected postID: 1, got %q.\", postID)\n\t}\n\n\tif post.category != cat1 {\n\t\tt.Fatalf(\"expected categoryID: %q, got %q.\", cat1, post.category)\n\t}\n}\n\nfunc toPostIDsStr(posts []*Post) string {\n\tvar postIDs []string\n\tfor _, post := range posts {\n\t\tpostIDs = append(postIDs, post.id.String())\n\t}\n\n\tpostIDsStr := strings.Join(postIDs, \",\")\n\treturn postIDsStr\n}\n\nfunc testGetPosts(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID := CreateFeed(\"teritori10\")\n\tfeed := mustGetFeed(feedID)\n\n\tCreatePost(feedID, rootPostID, cat1, \"post1\")\n\tCreatePost(feedID, rootPostID, cat1, \"post2\")\n\tCreatePost(feedID, rootPostID, cat1, \"post3\")\n\tCreatePost(feedID, rootPostID, cat1, \"post4\")\n\tCreatePost(feedID, rootPostID, cat1, \"post5\")\n\tpostIDToFlagged := CreatePost(feedID, rootPostID, cat1, \"post6\")\n\tpostIDToHide := CreatePost(feedID, rootPostID, cat1, \"post7\")\n\tCreatePost(feedID, rootPostID, cat1, \"post8\")\n\n\tvar posts []*Post\n\tvar postIDsStr string\n\n\t// Query last 3 posts\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,7,6\" {\n\t\tt.Fatalf(\"expected posts order: 8,7,6. Got: %s\", postIDsStr)\n\t}\n\n\t// Query page 2\n\tposts = getPosts(feed, 0, \"\", \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\tif postIDsStr != \"5,4,3\" {\n\t\tt.Fatalf(\"expected posts order: 5,4,3. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude hidden post\n\tHidePostForMe(feed.id, postIDToHide)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,6,5\" {\n\t\tt.Fatalf(\"expected posts order: 8,6,5. Got: %s\", postIDsStr)\n\t}\n\n\t// Exclude flagged post\n\tFlagPost(feed.id, postIDToFlagged)\n\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 0, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"8,5,4\" {\n\t\tt.Fatalf(\"expected posts order: 8,5,4. Got: %s\", postIDsStr)\n\t}\n\n\t// Pagination with hidden/flagged posts\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 3, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"3,2,1\" {\n\t\tt.Fatalf(\"expected posts order: 3,2,1. Got: %s\", postIDsStr)\n\t}\n\n\t// Query out of range\n\tposts = getPosts(feed, 0, user.String(), \"\", []uint64{}, 6, 3)\n\tpostIDsStr = toPostIDsStr(posts)\n\n\tif postIDsStr != \"\" {\n\t\tt.Fatalf(\"expected posts order: ''. Got: %s\", postIDsStr)\n\t}\n}\n\nfunc testReactPost(t *testing.T) {\n\tfeed := getFeed1()\n\tpost := getPost1()\n\n\ticon := \"🥰\"\n\tReactPost(feed.id, post.id, icon, true)\n\n\t// Set reaction\n\treactionCount_, ok := post.reactions.Get(\"🥰\")\n\tif !ok {\n\t\tt.Fatalf(\"expected 🥰 exists\")\n\t}\n\n\treactionCount := reactionCount_.(int)\n\tif reactionCount != 1 {\n\t\tt.Fatalf(\"expected reactionCount: 1, got %q.\", reactionCount)\n\t}\n\n\t// Unset reaction\n\tReactPost(feed.id, post.id, icon, false)\n\t_, exist := post.reactions.Get(\"🥰\")\n\tif exist {\n\t\tt.Fatalf(\"expected 🥰 not exist\")\n\t}\n}\n\nfunc testCreateAndDeleteComment(t *testing.T) {\n\tfeed1 := getFeed1()\n\tpost1 := getPost1()\n\n\tmetadata := `empty_meta_data`\n\n\tcommentID1 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcommentID2 := CreatePost(feed1.id, post1.id, cat1, metadata)\n\tcomment2 := feed1.MustGetPost(commentID2)\n\n\tif comment2.id != 3 { // 1 post + 2 comments = 3\n\t\tt.Fatalf(\"expected comment postID: 3, got %q.\", comment2.id)\n\t}\n\n\tif comment2.parentID != post1.id {\n\t\tt.Fatalf(\"expected comment parentID: %q, got %q.\", post1.id, comment2.parentID)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 2 {\n\t\tt.Fatalf(\"expected comments count: 2, got %d.\", post1.commentsCount)\n\t}\n\n\t// Get comments\n\tcomments := GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed := ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 2 {\n\t\tt.Fatalf(\"expected encoded comments: 2, got %q.\", commentsParsed)\n\t}\n\n\t// Delete 1 comment\n\tDeletePost(feed1.id, comment2.id)\n\tcomments = GetComments(feed1.id, post1.id, 0, 10)\n\tcommentsParsed = ujson.ParseSlice(comments)\n\n\tif len(commentsParsed) != 1 {\n\t\tt.Fatalf(\"expected encoded comments: 1, got %q.\", commentsParsed)\n\t}\n\n\t// Check comment count on parent\n\tif post1.commentsCount != 1 {\n\t\tt.Fatalf(\"expected comments count: 1, got %d.\", post1.commentsCount)\n\t}\n}\n\nfunc countPosts(feedID FeedID, categories []uint64, limit uint8) int {\n\toffset := uint64(0)\n\n\tpostsStr := GetPosts(feedID, 0, \"\", categories, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc countPostsByUser(feedID FeedID, user string) int {\n\toffset := uint64(0)\n\tlimit := uint8(10)\n\n\tpostsStr := GetPosts(feedID, 0, user, []uint64{}, offset, limit)\n\tif postsStr == \"[]\" {\n\t\treturn 0\n\t}\n\n\tparsedPosts := ujson.ParseSlice(postsStr)\n\tpostsCount := len(parsedPosts)\n\treturn postsCount\n}\n\nfunc testFilterByCategories(t *testing.T) {\n\t// // Re-add reaction to test post list\n\t// ReactPost(1, postID, \"🥰\", true)\n\t// ReactPost(1, postID, \"😇\", true)\n\n\tfilter_cat1 := []uint64{1}\n\tfilter_cat1_2 := []uint64{1, 2}\n\tfilter_cat9 := []uint64{9}\n\tfilter_cat1_2_9 := []uint64{1, 2, 9}\n\n\tfeedID2 := CreateFeed(\"teritori2\")\n\tfeed2 := mustGetFeed(feedID2)\n\n\t// Create 2 posts on root with cat1\n\tpostID1 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\tpostID2 := CreatePost(feed2.id, rootPostID, cat1, \"metadata\")\n\n\t// Create 1 posts on root with cat2\n\tpostID3 := CreatePost(feed2.id, rootPostID, cat2, \"metadata\")\n\n\t// Create comments on post 1\n\tcommentPostID1 := CreatePost(feed2.id, postID1, cat1, \"metadata\")\n\n\t// cat1: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1, 1); count != 1 {\n\t\tt.Fatalf(\"expected posts count: 1, got %q.\", count)\n\t}\n\n\t// cat1: Should return max = total\n\tif count := countPosts(feed2.id, filter_cat1, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = limit\n\tif count := countPosts(feed2.id, filter_cat1_2, 2); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// cat 1 + 2: Should return max = total on both\n\tif count := countPosts(feed2.id, filter_cat1_2, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 1, 2, 9: Should return total of 1, 2\n\tif count := countPosts(feed2.id, filter_cat1_2_9, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// cat 9: Should return 0\n\tif count := countPosts(feed2.id, filter_cat9, 10); count != 0 {\n\t\tt.Fatalf(\"expected posts count: 0, got %q.\", count)\n\t}\n\n\t// cat all: should return all\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// add comments should not impact the results\n\tCreatePost(feed2.id, postID1, cat1, \"metadata\")\n\tCreatePost(feed2.id, postID2, cat1, \"metadata\")\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 3 {\n\t\tt.Fatalf(\"expected posts count: 3, got %q.\", count)\n\t}\n\n\t// delete a post should affect the result\n\tDeletePost(feed2.id, postID1)\n\n\tif count := countPosts(feed2.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n}\n\nfunc testTipPost(t *testing.T) {\n\tcreator := testutils.TestAddress(\"creator\")\n\tstd.TestIssueCoins(creator, std.Coins{{\"ugnot\", 100_000_000}})\n\n\t// NOTE: Dont know why the address should be this to be able to call banker (= std.CallerAt(1))\n\ttipper := testutils.TestAddress(\"tipper\")\n\tstd.TestIssueCoins(tipper, std.Coins{{\"ugnot\", 50_000_000}})\n\n\tbanker := std.GetBanker(std.BankerTypeReadonly)\n\n\t// Check Original coins of creator/tipper\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 100_000_000 {\n\t\tt.Fatalf(\"expected creator coin count: 100_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\tif coins := banker.GetCoins(tipper); coins[0].Amount != 50_000_000 {\n\t\tt.Fatalf(\"expected tipper coin count: 50_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Creator creates feed, post\n\tstd.TestSetOrigCaller(creator)\n\n\tfeedID3 := CreateFeed(\"teritori3\")\n\tfeed3 := mustGetFeed(feedID3)\n\n\tpostID1 := CreatePost(feed3.id, rootPostID, cat1, \"metadata\")\n\tpost1 := feed3.MustGetPost(postID1)\n\n\t// Tiper tips the ppst\n\tstd.TestSetOrigCaller(tipper)\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 1_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\t// Coin must be increased for creator\n\tif coins := banker.GetCoins(creator); coins[0].Amount != 101_000_000 {\n\t\tt.Fatalf(\"expected creator coin after beging tipped: 101_000_000, got %d.\", coins[0].Amount)\n\t}\n\n\t// Total tip amount should increased\n\tif post1.tipAmount != 1_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 1_000_000, got %d.\", post1.tipAmount)\n\t}\n\n\t// Add more tip should update this total\n\tstd.TestSetOrigSend(std.Coins{{\"ugnot\", 2_000_000}}, nil)\n\tTipPost(feed3.id, post1.id)\n\n\tif post1.tipAmount != 3_000_000 {\n\t\tt.Fatalf(\"expected total tipAmount: 3_000_000, got %d.\", post1.tipAmount)\n\t}\n}\n\nfunc testFlagPost(t *testing.T) {\n\tflagger := testutils.TestAddress(\"flagger\")\n\n\tfeedID9 := CreateFeed(\"teritori9\")\n\tfeed9 := mustGetFeed(feedID9)\n\n\tCreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\tpid := CreatePost(feed9.id, rootPostID, cat1, \"metadata1\")\n\n\t// Flag post\n\tstd.TestSetOrigCaller(flagger)\n\tFlagPost(feed9.id, pid)\n\n\t// Another user flags\n\tanother := testutils.TestAddress(\"another\")\n\tstd.TestSetOrigCaller(another)\n\tFlagPost(feed9.id, pid)\n\n\tflaggedPostsStr := GetFlaggedPosts(feed9.id, 0, 10)\n\tparsed := ujson.ParseSlice(flaggedPostsStr)\n\tif flaggedPostsCount := len(parsed); flaggedPostsCount != 1 {\n\t\tt.Fatalf(\"expected flagged posts: 1, got %d.\", flaggedPostsCount)\n\t}\n}\n\nfunc testFilterUser(t *testing.T) {\n\tuser1 := testutils.TestAddress(\"user1\")\n\tuser2 := testutils.TestAddress(\"user2\")\n\n\t// User1 create 2 posts\n\tstd.TestSetOrigCaller(user1)\n\n\tfeedID4 := CreateFeed(\"teritori4\")\n\tfeed4 := mustGetFeed(feedID4)\n\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata2\": \"value\"}`)\n\n\t// User2 create 1 post\n\tstd.TestSetOrigCaller(user2)\n\tCreatePost(feed4.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPostsByUser(feed4.id, user1.String()); count != 2 {\n\t\tt.Fatalf(\"expected total posts by user1: 2, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, user2.String()); count != 1 {\n\t\tt.Fatalf(\"expected total posts by user2: 1, got %d.\", count)\n\t}\n\n\tif count := countPostsByUser(feed4.id, \"\"); count != 3 {\n\t\tt.Fatalf(\"expected total posts: 3, got %d.\", count)\n\t}\n}\n\nfunc testHidePostForMe(t *testing.T) {\n\tuser := std.Address(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tfeedID8 := CreateFeed(\"teritor8\")\n\tfeed8 := mustGetFeed(feedID8)\n\n\tpostIDToHide := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\tpostID := CreatePost(feed8.id, rootPostID, cat1, `{\"metadata\": \"value\"}`)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count: 2, got %q.\", count)\n\t}\n\n\t// Hide a post for me\n\tHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 1 {\n\t\tt.Fatalf(\"expected posts count after hidding: 1, got %q.\", count)\n\t}\n\n\t// Query from another user should return full list\n\tanother := std.Address(\"another\")\n\tstd.TestSetOrigCaller(another)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count from another: 2, got %q.\", count)\n\t}\n\n\t// UnHide a post for me\n\tstd.TestSetOrigCaller(user)\n\tUnHidePostForMe(feed8.id, postIDToHide)\n\n\tif count := countPosts(feed8.id, filter_all, 10); count != 2 {\n\t\tt.Fatalf(\"expected posts count after unhidding: 2, got %q.\", count)\n\t}\n}\n\nfunc testMigrateFeedData(t *testing.T) string {\n\tfeedID := feedsV7.CreateFeed(\"teritor11\")\n\n\t// Post to test\n\tpostID := feedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{\"metadata\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, postID, \"🇬🇸\", true)\n\n\t// Add comment to post\n\tcommentID := feedsV7.CreatePost(feedID, postID, 2, `{\"comment1\": \"value\"}`)\n\tfeedsV7.ReactPost(feedID, commentID, \"🇬🇸\", true)\n\n\t// // Post with json metadata\n\tfeedsV7.CreatePost(feedID, feedsV7.PostID(0), 2, `{'a':1}`)\n\n\t// Expect: should convert feed data to JSON successfully without error\n\tdataJSON := feedsV7.ExportFeedData(feedID)\n\tif dataJSON == \"\" {\n\t\tt.Fatalf(\"expected feed data exported successfully\")\n\t}\n\n\t// Import data =====================================\n\tImportFeedData(FeedID(uint64(feedID)), dataJSON)\n\n\t// Test public func\n\t// MigrateFromPreviousFeed(feedID)\n}\n\nfunc Test(t *testing.T) {\n\ttestCreateFeed(t)\n\n\ttestCreatePost(t)\n\n\ttestGetPosts(t)\n\n\ttestReactPost(t)\n\n\ttestCreateAndDeleteComment(t)\n\n\ttestFilterByCategories(t)\n\n\ttestTipPost(t)\n\n\ttestFilterUser(t)\n\n\ttestFlagPost(t)\n\n\ttestHidePostForMe(t)\n\n\ttestMigrateFeedData(t)\n}\n" }, { "Name": "flags.gno", @@ -60,11 +66,11 @@ stdout OK! }, { "Name": "post.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.GetOrigSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\tujson \"gno.land/p/demo/teritori/ujson\"\n)\n\ntype PostID uint64\n\nfunc (pid PostID) String() string {\n\treturn strconv.Itoa(int(pid))\n}\n\nfunc (pid *PostID) FromJSON(ast *ujson.JSONASTNode) {\n\tval, err := strconv.Atoi(ast.Value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t*pid = PostID(val)\n}\n\nfunc (pid PostID) ToJSON() string {\n\treturn strconv.Itoa(int(pid))\n}\n\ntype Reaction struct {\n\ticon string\n\tcount uint64\n}\n\nvar Categories []string = []string{\n\t\"Reaction\",\n\t\"Comment\",\n\t\"Normal\",\n\t\"Article\",\n\t\"Picture\",\n\t\"Audio\",\n\t\"Video\",\n}\n\ntype Post struct {\n\tid PostID\n\tparentID PostID\n\tfeedID FeedID\n\tcategory uint64\n\tmetadata string\n\treactions avl.Tree // icon -> count\n\tcomments avl.Tree // Post.id -> *Post\n\tcreator std.Address\n\ttipAmount uint64\n\tdeleted bool\n\tcommentsCount uint64\n\n\tcreatedAt int64\n\tupdatedAt int64\n\tdeletedAt int64\n}\n\nfunc newPost(feed *Feed, id PostID, creator std.Address, parentID PostID, category uint64, metadata string) *Post {\n\treturn &Post{\n\t\tid: id,\n\t\tparentID: parentID,\n\t\tfeedID: feed.id,\n\t\tcategory: category,\n\t\tmetadata: metadata,\n\t\treactions: avl.Tree{},\n\t\tcreator: creator,\n\t\tcreatedAt: time.Now().Unix(),\n\t}\n}\n\nfunc (post *Post) String() string {\n\treturn post.ToJSON()\n}\n\nfunc (post *Post) Update(category uint64, metadata string) {\n\tpost.category = category\n\tpost.metadata = metadata\n\tpost.updatedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Delete() {\n\tpost.deleted = true\n\tpost.deletedAt = time.Now().Unix()\n}\n\nfunc (post *Post) Tip(from std.Address, to std.Address) {\n\treceivedCoins := std.OriginSend()\n\tamount := receivedCoins[0].Amount\n\n\tbanker := std.GetBanker(std.BankerTypeOrigSend)\n\t// banker := std.GetBanker(std.BankerTypeRealmSend)\n\tcoinsToSend := std.Coins{std.Coin{Denom: \"ugnot\", Amount: amount}}\n\tpkgaddr := std.GetOrigPkgAddr()\n\n\tbanker.SendCoins(pkgaddr, to, coinsToSend)\n\n\t// Update tip amount\n\tpost.tipAmount += uint64(amount)\n}\n\n// Always remove reaction if count = 0\nfunc (post *Post) React(icon string, up bool) {\n\tcount_, ok := post.reactions.Get(icon)\n\tcount := 0\n\n\tif ok {\n\t\tcount = count_.(int)\n\t}\n\n\tif up {\n\t\tcount++\n\t} else {\n\t\tcount--\n\t}\n\n\tif count <= 0 {\n\t\tpost.reactions.Remove(icon)\n\t} else {\n\t\tpost.reactions.Set(icon, count)\n\t}\n}\n\nfunc (post *Post) Render() string {\n\treturn post.metadata\n}\n\nfunc (post *Post) FromJSON(jsonData string) {\n\tast := ujson.TokenizeAndParse(jsonData)\n\tast.ParseObject([]*ujson.ParseKV{\n\t\t{Key: \"id\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.id = PostID(pid)\n\t\t}},\n\t\t{Key: \"parentID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tpid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.parentID = PostID(pid)\n\t\t}},\n\t\t{Key: \"feedID\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\tfid, _ := strconv.Atoi(node.Value)\n\t\t\tpost.feedID = FeedID(fid)\n\t\t}},\n\t\t{Key: \"category\", Value: &post.category},\n\t\t{Key: \"metadata\", Value: &post.metadata},\n\t\t{Key: \"reactions\", CustomParser: func(node *ujson.JSONASTNode) {\n\t\t\treactions := avl.NewTree()\n\t\t\tfor _, child := range node.ObjectChildren {\n\t\t\t\treactionCount := child.Value\n\t\t\t\treactions.Set(child.Key, reactionCount)\n\t\t\t}\n\t\t\tpost.reactions = *reactions\n\t\t}},\n\t\t{Key: \"commentsCount\", Value: &post.commentsCount},\n\t\t{Key: \"creator\", Value: &post.creator},\n\t\t{Key: \"tipAmount\", Value: &post.tipAmount},\n\t\t{Key: \"deleted\", Value: &post.deleted},\n\t\t{Key: \"createdAt\", Value: &post.createdAt},\n\t\t{Key: \"updatedAt\", Value: &post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: &post.deletedAt},\n\t})\n}\n\nfunc (post *Post) ToJSON() string {\n\treactionsKV := []ujson.FormatKV{}\n\tpost.reactions.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcount := value.(int)\n\t\tdata := ujson.FormatKV{Key: key, Value: count}\n\t\treactionsKV = append(reactionsKV, data)\n\t\treturn false\n\t})\n\treactions := ujson.FormatObject(reactionsKV)\n\n\tpostJSON := ujson.FormatObject([]ujson.FormatKV{\n\t\t{Key: \"id\", Value: uint64(post.id)},\n\t\t{Key: \"parentID\", Value: uint64(post.parentID)},\n\t\t{Key: \"feedID\", Value: uint64(post.feedID)},\n\t\t{Key: \"category\", Value: post.category},\n\t\t{Key: \"metadata\", Value: post.metadata},\n\t\t{Key: \"reactions\", Value: reactions, Raw: true},\n\t\t{Key: \"creator\", Value: post.creator},\n\t\t{Key: \"tipAmount\", Value: post.tipAmount},\n\t\t{Key: \"deleted\", Value: post.deleted},\n\t\t{Key: \"commentsCount\", Value: post.commentsCount},\n\t\t{Key: \"createdAt\", Value: post.createdAt},\n\t\t{Key: \"updatedAt\", Value: post.updatedAt},\n\t\t{Key: \"deletedAt\", Value: post.deletedAt},\n\t})\n\treturn postJSON\n}\n" }, { "Name": "public.gno", - "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PrevRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PrevRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PrevRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PrevRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PrevRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PrevRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PrevRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" + "Body": "package social_feeds\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/teritori/flags_index\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// Only registered user can create a new feed\n// For the flexibility when testing, allow all user to create feed\nfunc CreateFeed(name string) FeedID {\n\tpkgpath := std.CurrentRealmPath()\n\n\tfid := incGetFeedID()\n\tcaller := std.PreviousRealm().Addr()\n\turl := strings.Replace(pkgpath, \"gno.land\", \"\", -1) + \":\" + name\n\tfeed := newFeed(fid, url, name, caller)\n\tfidkey := feedIDKey(fid)\n\tgFeeds.Set(fidkey, feed)\n\tgFeedsByName.Set(name, feed)\n\treturn feed.id\n}\n\n// Anyone can create a post in a existing feed, allow un-registered users also\nfunc CreatePost(fid FeedID, parentID PostID, catetory uint64, metadata string) PostID {\n\tcaller := std.PreviousRealm().Addr()\n\n\tfeed := mustGetFeed(fid)\n\tpost := feed.AddPost(caller, parentID, catetory, metadata)\n\treturn post.id\n}\n\n// Only post's owner can edit post\nfunc EditPost(fid FeedID, pid PostID, category uint64, metadata string) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator {\n\t\tpanic(\"you are not creator of this post\")\n\t}\n\n\tpost.Update(category, metadata)\n}\n\n// Only feed creator/owner can call this\nfunc SetOwner(fid FeedID, newOwner std.Address) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tif caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are not creator/owner of this feed\")\n\t}\n\n\tfeed.owner = newOwner\n}\n\n// Only feed creator/owner or post creator can delete the post\nfunc DeletePost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tif caller != post.creator && caller != feed.creator && caller != feed.owner {\n\t\tpanic(\"you are nor creator of this post neither creator/owner of the feed\")\n\t}\n\n\tpost.Delete()\n\n\t// If post is comment then decrease comments count on parent\n\tif uint64(post.parentID) != 0 {\n\t\tparent := feed.MustGetPost(post.parentID)\n\t\tparent.commentsCount -= 1\n\t}\n}\n\n// Only feed owner can ban the post\nfunc BanPost(fid FeedID, pid PostID, reason string) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\t_ = feed.MustGetPost(pid)\n\n\t// For experimenting, we ban only the post for now\n\t// TODO: recursive delete/ban comments\n\tif caller != feed.owner {\n\t\tpanic(\"you are owner of the feed\")\n\t}\n\n\tfeed.BanPost(pid)\n\n\tfeed.flags.ClearFlagCount(getFlagID(fid, pid))\n}\n\n// Any one can react post\nfunc ReactPost(fid FeedID, pid PostID, icon string, up bool) {\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.React(icon, up)\n}\n\nfunc TipPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\tpost := feed.MustGetPost(pid)\n\n\tpost.Tip(caller, post.creator)\n}\n\n// Get a list of flagged posts\n// NOTE: We can support multi feeds in the future but for now we will have only 1 feed\n// Return stringified list in format: postStr-count,postStr-count\nfunc GetFlaggedPosts(fid FeedID, offset uint64, limit uint8) string {\n\tfeed := mustGetFeed(fid)\n\n\t// Already sorted by count descending\n\tflags := feed.flags.GetFlags(uint64(limit), offset)\n\n\tvar postList []string\n\tfor _, flagCount := range flags {\n\t\tflagID := flagCount.FlagID\n\n\t\tfeedID, postID := parseFlagID(flagID)\n\t\tif feedID != feed.id {\n\t\t\tcontinue\n\t\t}\n\n\t\tpost := feed.GetPost(postID)\n\t\tpostList = append(postList, ufmt.Sprintf(\"%s\", post))\n\t}\n\n\tSEPARATOR := \",\"\n\tres := strings.Join(postList, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// NOTE: due to bug of std.PreviousRealm().Addr() return \"\" when query so we user this proxy function temporary\n// in waiting of correct behaviour of std.PreviousRealm().Addr()\nfunc GetPosts(fid FeedID, parentID PostID, user string, categories []uint64, offset uint64, limit uint8) string {\n\tcaller := std.PreviousRealm().Addr()\n\tdata := GetPostsWithCaller(fid, parentID, caller.String(), user, categories, offset, limit)\n\treturn data\n}\n\nfunc GetPostsWithCaller(fid FeedID, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) string {\n\t// Return flagged posts, we process flagged posts differently using FlagIndex\n\tif len(categories) == 1 && categories[0] == uint64(9) {\n\t\treturn GetFlaggedPosts(fid, offset, limit)\n\t}\n\n\t// BUG: normally std.PreviousRealm().Addr() should return a value instead of empty\n\t// Fix is in progress on Gno side\n\tfeed := mustGetFeed(fid)\n\tposts := getPosts(feed, parentID, callerAddrStr, user, categories, offset, limit)\n\n\tSEPARATOR := \",\"\n\tvar postListStr []string\n\n\tfor _, post := range posts {\n\t\tpostListStr = append(postListStr, post.String())\n\t}\n\n\tres := strings.Join(postListStr, SEPARATOR)\n\treturn ufmt.Sprintf(\"[%s]\", res)\n}\n\n// user here is: filter by user\nfunc getPosts(feed *Feed, parentID PostID, callerAddrStr string, user string, categories []uint64, offset uint64, limit uint8) []*Post {\n\tcaller := std.Address(callerAddrStr)\n\n\tvar posts []*Post\n\tvar skipped uint64\n\n\t// Create an avlTree for optimizing the check\n\trequestedCategories := avl.NewTree()\n\tfor _, category := range categories {\n\t\tcatStr := strconv.FormatUint(category, 10)\n\t\trequestedCategories.Set(catStr, true)\n\t}\n\n\tfeed.posts.ReverseIterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tpost := value.(*Post)\n\n\t\tpostCatStr := strconv.FormatUint(post.category, 10)\n\n\t\t// NOTE: this search mechanism is not efficient, only for demo purpose\n\t\tif post.parentID == parentID && post.deleted == false {\n\t\t\tif requestedCategories.Size() > 0 && !requestedCategories.Has(postCatStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif user != \"\" && std.Address(user) != post.creator {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Filter hidden post\n\t\t\tflagID := getFlagID(feed.id, post.id)\n\t\t\tif feed.flags.HasFlagged(flagID, callerAddrStr) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\t// Check if post is in hidden list\n\t\t\tvalue, exists := feed.hiddenPostsByUser.Get(caller.String())\n\t\t\tif exists {\n\t\t\t\thiddenPosts := value.(*avl.Tree)\n\t\t\t\t// If post.id exists in hiddenPosts tree => that post is hidden\n\t\t\t\tif hiddenPosts.Has(post.id.String()) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif skipped < offset {\n\t\t\t\tskipped++\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tposts = append(posts, post)\n\t\t}\n\n\t\tif len(posts) == int(limit) {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n\n\treturn posts\n}\n\n// Get comments list\nfunc GetComments(fid FeedID, parentID PostID, offset uint64, limit uint8) string {\n\treturn GetPosts(fid, parentID, \"\", []uint64{}, offset, limit)\n}\n\n// Get Post\nfunc GetPost(fid FeedID, pid PostID) string {\n\tfeed := mustGetFeed(fid)\n\n\tdata, ok := feed.posts.Get(postIDKey(pid))\n\tif !ok {\n\t\tpanic(\"Unable to get post\")\n\t}\n\n\tpost := data.(*Post)\n\treturn post.String()\n}\n\nfunc FlagPost(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.FlagPost(caller, pid)\n}\n\nfunc HidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.HidePostForUser(caller, pid)\n}\n\nfunc UnHidePostForMe(fid FeedID, pid PostID) {\n\tcaller := std.PreviousRealm().Addr()\n\tfeed := mustGetFeed(fid)\n\n\tfeed.UnHidePostForUser(caller, pid)\n}\n\nfunc GetFlags(fid FeedID, limit uint64, offset uint64) string {\n\tfeed := mustGetFeed(fid)\n\n\ttype FlagCount struct {\n\t\tFlagID flags_index.FlagID\n\t\tCount uint64\n\t}\n\n\tflags := feed.flags.GetFlags(limit, offset)\n\n\tvar res []string\n\tfor _, flag := range flags {\n\t\tres = append(res, ufmt.Sprintf(\"%s:%d\", flag.FlagID, flag.Count))\n\t}\n\n\treturn strings.Join(res, \"|\")\n}\n\n// TODO: allow only creator to call\nfunc GetFeedByID(fid FeedID) *Feed {\n\treturn mustGetFeed(fid)\n}\n\n// TODO: allow only admin to call\nfunc ExportFeedData(fid FeedID) string {\n\tfeed := mustGetFeed(fid)\n\tfeedJSON := feed.ToJSON()\n\treturn feedJSON\n}\n\n// TODO: allow only admin to call\nfunc ImportFeedData(fid FeedID, jsonData string) {\n\tfeed := mustGetFeed(fid)\n\tfeed.FromJSON(jsonData)\n}\n\n// func MigrateFromPreviousFeed(fid feedsV7.FeedID) {\n// \t// Get exported data from previous feeds\n// \tjsonData := feedsV7.ExportFeedData(fid)\n// \tImportFeedData(FeedID(uint64(fid)), jsonData)\n// }\n" }, { "Name": "render.gno", @@ -101,3 +107,1232 @@ import ( func Call(s string) { base64.StdEncoding.DecodeString("hey") } + +-- pusers/gno.mod -- +module gno.land/p/demo/users + +-- pusers/users.gno -- +package users + +import ( + "std" + "strconv" +) + +//---------------------------------------- +// Types + +type User struct { + Address std.Address + Name string + Profile string + Number int + Invites int + Inviter std.Address +} + +func (u *User) Render() string { + str := "## user " + u.Name + "\n" + + "\n" + + " * address = " + string(u.Address) + "\n" + + " * " + strconv.Itoa(u.Invites) + " invites\n" + if u.Inviter != "" { + str = str + " * invited by " + string(u.Inviter) + "\n" + } + str = str + "\n" + + u.Profile + "\n" + return str +} + +type AddressOrName string + +func (aon AddressOrName) IsName() bool { + return aon != "" && aon[0] == '@' +} + +func (aon AddressOrName) GetName() (string, bool) { + if len(aon) >= 2 && aon[0] == '@' { + return string(aon[1:]), true + } + return "", false +} + +-- users/gno.mod -- +module gno.land/r/demo/users + +-- users/users.gno -- +package users + +import ( + "regexp" + "std" + "strconv" + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/demo/avl/pager" + "gno.land/p/demo/avlhelpers" + "gno.land/p/demo/users" +) + +//---------------------------------------- +// State + +var ( + admin std.Address = "g1manfred47kzduec920z88wfr64ylksmdcedlf5" // @moul + + restricted avl.Tree // Name -> true - restricted name + name2User avl.Tree // Name -> *users.User + addr2User avl.Tree // std.Address -> *users.User + invites avl.Tree // string(inviter+":"+invited) -> true + counter int // user id counter + minFee int64 = 20 * 1_000_000 // minimum gnot must be paid to register. + maxFeeMult int64 = 10 // maximum multiples of minFee accepted. +) + +//---------------------------------------- +// Top-level functions + +func Register(inviter std.Address, name string, profile string) { + // assert CallTx call. + std.AssertOriginCall() + // assert invited or paid. + caller := std.CallerAt(2) + if caller != std.OriginCaller() { + panic("should not happen") // because std.AssertOrigCall(). + } + + sentCoins := std.OriginSend() + minCoin := std.NewCoin("ugnot", minFee) + + if inviter == "" { + // banker := std.GetBanker(std.BankerTypeOrigSend) + if len(sentCoins) == 1 && sentCoins[0].IsGTE(minCoin) { + if sentCoins[0].Amount > minFee*maxFeeMult { + panic("payment must not be greater than " + strconv.Itoa(int(minFee*maxFeeMult))) + } else { + // ok + } + } else { + panic("payment must not be less than " + strconv.Itoa(int(minFee))) + } + } else { + invitekey := inviter.String() + ":" + caller.String() + _, ok := invites.Get(invitekey) + if !ok { + panic("invalid invitation") + } + invites.Remove(invitekey) + } + + // assert not already registered. + _, ok := name2User.Get(name) + if ok { + panic("name already registered: " + name) + } + _, ok = addr2User.Get(caller.String()) + if ok { + panic("address already registered: " + caller.String()) + } + + isInviterAdmin := inviter == admin + + // check for restricted name + if _, isRestricted := restricted.Get(name); isRestricted { + // only address invite by the admin can register restricted name + if !isInviterAdmin { + panic("restricted name: " + name) + } + + restricted.Remove(name) + } + + // assert name is valid. + // admin inviter can bypass name restriction + if !isInviterAdmin && !reName.MatchString(name) { + panic("invalid name: " + name + " (must be at least 6 characters, lowercase alphanumeric with underscore)") + } + + // remainder of fees go toward invites. + invites := int(0) + if len(sentCoins) == 1 { + if sentCoins[0].Denom == "ugnot" && sentCoins[0].Amount >= minFee { + invites = int(sentCoins[0].Amount / minFee) + if inviter == "" && invites > 0 { + invites -= 1 + } + } + } + // register. + counter++ + user := &users.User{ + Address: caller, + Name: name, + Profile: profile, + Number: counter, + Invites: invites, + Inviter: inviter, + } + name2User.Set(name, user) + addr2User.Set(caller.String(), user) +} + +func Invite(invitee string) { + // assert CallTx call. + std.AssertOriginCall() + // get caller/inviter. + caller := std.CallerAt(2) + if caller != std.OriginCaller() { + panic("should not happen") // because std.AssertOrigCall(). + } + lines := strings.Split(invitee, "\n") + if caller == admin { + // nothing to do, all good + } else { + // ensure has invites. + userI, ok := addr2User.Get(caller.String()) + if !ok { + panic("user unknown") + } + user := userI.(*users.User) + if user.Invites <= 0 { + panic("user has no invite tokens") + } + user.Invites -= len(lines) + if user.Invites < 0 { + panic("user has insufficient invite tokens") + } + } + // for each line... + for _, line := range lines { + if line == "" { + continue // file bodies have a trailing newline. + } else if strings.HasPrefix(line, `//`) { + continue // comment + } + // record invite. + invitekey := string(caller) + ":" + string(line) + invites.Set(invitekey, true) + } +} + +func GrantInvites(invites string) { + // assert CallTx call. + std.AssertOriginCall() + // assert admin. + caller := std.CallerAt(2) + if caller != std.OriginCaller() { + panic("should not happen") // because std.AssertOrigCall(). + } + if caller != admin { + panic("unauthorized") + } + // for each line... + lines := strings.Split(invites, "\n") + for _, line := range lines { + if line == "" { + continue // file bodies have a trailing newline. + } else if strings.HasPrefix(line, `//`) { + continue // comment + } + // parse name and invites. + var name string + var invites int + parts := strings.Split(line, ":") + if len(parts) == 1 { // short for :1. + name = parts[0] + invites = 1 + } else if len(parts) == 2 { + name = parts[0] + invites_, err := strconv.Atoi(parts[1]) + if err != nil { + panic(err) + } + invites = int(invites_) + } else { + panic("should not happen") + } + // give invites. + userI, ok := name2User.Get(name) + if !ok { + // maybe address. + userI, ok = addr2User.Get(name) + if !ok { + panic("invalid user " + name) + } + } + user := userI.(*users.User) + user.Invites += invites + } +} + +// Any leftover fees go toward invitations. +func SetMinFee(newMinFee int64) { + // assert CallTx call. + std.AssertOriginCall() + // assert admin caller. + caller := std.CallerAt(2) + if caller != admin { + panic("unauthorized") + } + // update global variables. + minFee = newMinFee +} + +// This helps prevent fat finger accidents. +func SetMaxFeeMultiple(newMaxFeeMult int64) { + // assert CallTx call. + std.AssertOriginCall() + // assert admin caller. + caller := std.CallerAt(2) + if caller != admin { + panic("unauthorized") + } + // update global variables. + maxFeeMult = newMaxFeeMult +} + +//---------------------------------------- +// Exposed public functions + +func GetUserByName(name string) *users.User { + userI, ok := name2User.Get(name) + if !ok { + return nil + } + return userI.(*users.User) +} + +func GetUserByAddress(addr std.Address) *users.User { + userI, ok := addr2User.Get(addr.String()) + if !ok { + return nil + } + return userI.(*users.User) +} + +// unlike GetUserByName, input must be "@" prefixed for names. +func GetUserByAddressOrName(input users.AddressOrName) *users.User { + name, isName := input.GetName() + if isName { + return GetUserByName(name) + } + return GetUserByAddress(std.Address(input)) +} + +// Get a list of user names starting from the given prefix. Limit the +// number of results to maxResults. (This can be used for a name search tool.) +func ListUsersByPrefix(prefix string, maxResults int) []string { + return avlhelpers.ListByteStringKeysByPrefix(&name2User, prefix, maxResults) +} + +func Resolve(input users.AddressOrName) std.Address { + name, isName := input.GetName() + if !isName { + return std.Address(input) // TODO check validity + } + + user := GetUserByName(name) + return user.Address +} + +// Add restricted name to the list +func AdminAddRestrictedName(name string) { + // assert CallTx call. + std.AssertOriginCall() + // get caller + caller := std.OriginCaller() + // assert admin + if caller != admin { + panic("unauthorized") + } + + if user := GetUserByName(name); user != nil { + panic("already registered name") + } + + // register restricted name + + restricted.Set(name, true) +} + +//---------------------------------------- +// Constants + +// NOTE: name length must be clearly distinguishable from a bech32 address. +var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{5,16}$`) + +//---------------------------------------- +// Render main page + +func Render(fullPath string) string { + path, _ := splitPathAndQuery(fullPath) + if path == "" { + return renderHome(fullPath) + } else if len(path) >= 38 { // 39? 40? + if path[:2] != "g1" { + return "invalid address " + path + } + user := GetUserByAddress(std.Address(path)) + if user == nil { + // TODO: display basic information about account. + return "unknown address " + path + } + return user.Render() + } else { + user := GetUserByName(path) + if user == nil { + return "unknown username " + path + } + return user.Render() + } +} + +func renderHome(path string) string { + doc := "" + + page := pager.NewPager(&name2User, 50, false).MustGetPageByPath(path) + + for _, item := range page.Items { + user := item.Value.(*users.User) + doc += " * [" + user.Name + "](/r/demo/users:" + user.Name + ")\n" + } + doc += "\n" + doc += page.Picker() + return doc +} + +func splitPathAndQuery(fullPath string) (string, string) { + parts := strings.SplitN(fullPath, "?", 2) + path := parts[0] + queryString := "" + if len(parts) > 1 { + queryString = "?" + parts[1] + } + return path, queryString +} + +// pre-restricted names +var preRestrictedNames = []string{ + "bitcoin", "cosmos", "newtendermint", "ethereum", +} + +// pre-registered users +var preRegisteredUsers = []struct { + Name string + Address std.Address +}{ + // system name + {"archives", "g1xlnyjrnf03ju82v0f98ruhpgnquk28knmjfe5k"}, // -> @r_archives + {"demo", "g13ek2zz9qurzynzvssyc4sthwppnruhnp0gdz8n"}, // -> @r_demo + {"gno", "g19602kd9tfxrfd60sgreadt9zvdyyuudcyxsz8a"}, // -> @r_gno + {"gnoland", "g1g3lsfxhvaqgdv4ccemwpnms4fv6t3aq3p5z6u7"}, // -> @r_gnoland + {"gnolang", "g1yjlnm3z2630gg5mryjd79907e0zx658wxs9hnd"}, // -> @r_gnolang + {"gov", "g1g73v2anukg4ej7axwqpthsatzrxjsh0wk797da"}, // -> @r_gov + {"nt", "g15ge0ae9077eh40erwrn2eq0xw6wupwqthpv34l"}, // -> @r_nt + {"sys", "g1r929wt2qplfawe4lvqv9zuwfdcz4vxdun7qh8l"}, // -> @r_sys + {"x", "g164sdpew3c2t3rvxj3kmfv7c7ujlvcw2punzzuz"}, // -> @r_x + + // test1 user + {"test1", "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"}, // -> @test1 +} + +func init() { + // add pre-registered users + for _, res := range preRegisteredUsers { + // assert not already registered. + _, ok := name2User.Get(res.Name) + if ok { + panic("name already registered") + } + + _, ok = addr2User.Get(res.Address.String()) + if ok { + panic("address already registered") + } + + counter++ + user := &users.User{ + Address: res.Address, + Name: res.Name, + Profile: "", + Number: counter, + Invites: int(0), + Inviter: admin, + } + name2User.Set(res.Name, user) + addr2User.Set(res.Address.String(), user) + } + + // add pre-restricted names + for _, name := range preRestrictedNames { + if _, ok := name2User.Get(name); ok { + panic("name already registered") + } + + restricted.Set(name, true) + } +} + +-- boards/gno.mod -- +module gno.land/r/demo/boards + +-- boards/boards.gno -- +package boards + +import ( + "std" + "strconv" + "regexp" + "time" + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/moul/txlink" + + "gno.land/r/demo/users" + +) + +//---------------------------------------- +// Board + +type BoardID uint64 + +func (bid BoardID) String() string { + return strconv.Itoa(int(bid)) +} + +type Board struct { + id BoardID // only set for public boards. + url string + name string + creator std.Address + threads avl.Tree // Post.id -> *Post + postsCtr uint64 // increments Post.id + createdAt time.Time + deleted avl.Tree // TODO reserved for fast-delete. +} + +func newBoard(id BoardID, url string, name string, creator std.Address) *Board { + if !reName.MatchString(name) { + panic("invalid name: " + name) + } + exists := gBoardsByName.Has(name) + if exists { + panic("board already exists") + } + return &Board{ + id: id, + url: url, + name: name, + creator: creator, + threads: avl.Tree{}, + createdAt: time.Now(), + deleted: avl.Tree{}, + } +} + +/* TODO support this once we figure out how to ensure URL correctness. +// A private board is not tracked by gBoards*, +// but must be persisted by the caller's realm. +// Private boards have 0 id and does not ping +// back the remote board on reposts. +func NewPrivateBoard(url string, name string, creator std.Address) *Board { + return newBoard(0, url, name, creator) +} +*/ + +func (board *Board) IsPrivate() bool { + return board.id == 0 +} + +func (board *Board) GetThread(pid PostID) *Post { + pidkey := postIDKey(pid) + postI, exists := board.threads.Get(pidkey) + if !exists { + return nil + } + return postI.(*Post) +} + +func (board *Board) AddThread(creator std.Address, title string, body string) *Post { + pid := board.incGetPostID() + pidkey := postIDKey(pid) + thread := newPost(board, pid, creator, title, body, pid, 0, 0) + board.threads.Set(pidkey, thread) + return thread +} + +// NOTE: this can be potentially very expensive for threads with many replies. +// TODO: implement optional fast-delete where thread is simply moved. +func (board *Board) DeleteThread(pid PostID) { + pidkey := postIDKey(pid) + _, removed := board.threads.Remove(pidkey) + if !removed { + panic("thread does not exist with id " + pid.String()) + } +} + +func (board *Board) HasPermission(addr std.Address, perm Permission) bool { + if board.creator == addr { + switch perm { + case EditPermission: + return true + case DeletePermission: + return true + default: + return false + } + } + return false +} + +// Renders the board for display suitable as plaintext in +// console. This is suitable for demonstration or tests, +// but not for prod. +func (board *Board) RenderBoard() string { + str := "" + str += "\\[[post](" + board.GetPostFormURL() + ")]\n\n" + if board.threads.Size() > 0 { + board.threads.Iterate("", "", func(key string, value interface{}) bool { + if str != "" { + str += "----------------------------------------\n" + } + str += value.(*Post).RenderSummary() + "\n" + return false + }) + } + return str +} + +func (board *Board) incGetPostID() PostID { + board.postsCtr++ + return PostID(board.postsCtr) +} + +func (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string { + if replyID == 0 { + return board.url + "/" + threadID.String() + } else { + return board.url + "/" + threadID.String() + "/" + replyID.String() + } +} + +func (board *Board) GetPostFormURL() string { + return txlink.Call("CreateThread", "bid", board.id.String()) +} + +var ( + gBoards avl.Tree // id -> *Board + gBoardsCtr int // increments Board.id + gBoardsByName avl.Tree // name -> *Board + gDefaultAnonFee = 100000000 // minimum fee required if anonymous +) + +//---------------------------------------- +// Constants + +var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`) + +//---------------------------------------- +// private utility methods +// XXX ensure these cannot be called from public. + +func getBoard(bid BoardID) *Board { + bidkey := boardIDKey(bid) + board_, exists := gBoards.Get(bidkey) + if !exists { + return nil + } + board := board_.(*Board) + return board +} + +func incGetBoardID() BoardID { + gBoardsCtr++ + return BoardID(gBoardsCtr) +} + +func padLeft(str string, length int) string { + if len(str) >= length { + return str + } else { + return strings.Repeat(" ", length-len(str)) + str + } +} + +func padZero(u64 uint64, length int) string { + str := strconv.Itoa(int(u64)) + if len(str) >= length { + return str + } else { + return strings.Repeat("0", length-len(str)) + str + } +} + +func boardIDKey(bid BoardID) string { + return padZero(uint64(bid), 10) +} + +func postIDKey(pid PostID) string { + return padZero(uint64(pid), 10) +} + +func indentBody(indent string, body string) string { + lines := strings.Split(body, "\n") + res := "" + for i, line := range lines { + if i > 0 { + res += "\n" + } + res += indent + line + } + return res +} + +// NOTE: length must be greater than 3. +func summaryOf(str string, length int) string { + lines := strings.SplitN(str, "\n", 2) + line := lines[0] + if len(line) > length { + line = line[:(length-3)] + "..." + } else if len(lines) > 1 { + // len(line) <= 80 + line = line + "..." + } + return line +} + +func displayAddressMD(addr std.Address) string { + user := users.GetUserByAddress(addr) + if user == nil { + return "[" + addr.String() + "](/r/demo/users:" + addr.String() + ")" + } else { + return "[@" + user.Name + "](/r/demo/users:" + user.Name + ")" + } +} + +func usernameOf(addr std.Address) string { + user := users.GetUserByAddress(addr) + if user == nil { + return "" + } + return user.Name +} + +//---------------------------------------- +// Post + +// NOTE: a PostID is relative to the board. +type PostID uint64 + +func (pid PostID) String() string { + return strconv.Itoa(int(pid)) +} + +// A Post is a "thread" or a "reply" depending on context. +// A thread is a Post of a Board that holds other replies. +type Post struct { + board *Board + id PostID + creator std.Address + title string // optional + body string + replies avl.Tree // Post.id -> *Post + repliesAll avl.Tree // Post.id -> *Post (all replies, for top-level posts) + reposts avl.Tree // Board.id -> Post.id + threadID PostID // original Post.id + parentID PostID // parent Post.id (if reply or repost) + repostBoard BoardID // original Board.id (if repost) + createdAt time.Time + updatedAt time.Time +} + +func newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post { + return &Post{ + board: board, + id: id, + creator: creator, + title: title, + body: body, + replies: avl.Tree{}, + repliesAll: avl.Tree{}, + reposts: avl.Tree{}, + threadID: threadID, + parentID: parentID, + repostBoard: repostBoard, + createdAt: time.Now(), + } +} + +func (post *Post) IsThread() bool { + return post.parentID == 0 +} + +func (post *Post) GetPostID() PostID { + return post.id +} + +func (post *Post) AddReply(creator std.Address, body string) *Post { + board := post.board + pid := board.incGetPostID() + pidkey := postIDKey(pid) + reply := newPost(board, pid, creator, "", body, post.threadID, post.id, 0) + post.replies.Set(pidkey, reply) + if post.threadID == post.id { + post.repliesAll.Set(pidkey, reply) + } else { + thread := board.GetThread(post.threadID) + thread.repliesAll.Set(pidkey, reply) + } + return reply +} + +func (post *Post) Update(title string, body string) { + post.title = title + post.body = body + post.updatedAt = time.Now() +} + +func (thread *Post) GetReply(pid PostID) *Post { + pidkey := postIDKey(pid) + replyI, ok := thread.repliesAll.Get(pidkey) + if !ok { + return nil + } else { + return replyI.(*Post) + } +} + +func (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post { + if !post.IsThread() { + panic("cannot repost non-thread post") + } + pid := dst.incGetPostID() + pidkey := postIDKey(pid) + repost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id) + dst.threads.Set(pidkey, repost) + if !dst.IsPrivate() { + bidkey := boardIDKey(dst.id) + post.reposts.Set(bidkey, pid) + } + return repost +} + +func (thread *Post) DeletePost(pid PostID) { + if thread.id == pid { + panic("should not happen") + } + pidkey := postIDKey(pid) + postI, removed := thread.repliesAll.Remove(pidkey) + if !removed { + panic("post not found in thread") + } + post := postI.(*Post) + if post.parentID != thread.id { + parent := thread.GetReply(post.parentID) + parent.replies.Remove(pidkey) + } else { + thread.replies.Remove(pidkey) + } +} + +func (post *Post) HasPermission(addr std.Address, perm Permission) bool { + if post.creator == addr { + switch perm { + case EditPermission: + return true + case DeletePermission: + return true + default: + return false + } + } + // post notes inherit permissions of the board. + return post.board.HasPermission(addr, perm) +} + +func (post *Post) GetSummary() string { + return summaryOf(post.body, 80) +} + +func (post *Post) GetURL() string { + if post.IsThread() { + return post.board.GetURLFromThreadAndReplyID( + post.id, 0) + } else { + return post.board.GetURLFromThreadAndReplyID( + post.threadID, post.id) + } +} + +func (post *Post) GetReplyFormURL() string { + return txlink.Call("CreateReply", + "bid", post.board.id.String(), + "threadid", post.threadID.String(), + "postid", post.id.String(), + ) +} + +func (post *Post) GetRepostFormURL() string { + return txlink.Call("CreateRepost", + "bid", post.board.id.String(), + "postid", post.id.String(), + ) +} + +func (post *Post) GetDeleteFormURL() string { + return txlink.Call("DeletePost", + "bid", post.board.id.String(), + "threadid", post.threadID.String(), + "postid", post.id.String(), + ) +} + +func (post *Post) RenderSummary() string { + if post.repostBoard != 0 { + dstBoard := getBoard(post.repostBoard) + if dstBoard == nil { + panic("repostBoard does not exist") + } + thread := dstBoard.GetThread(PostID(post.parentID)) + if thread == nil { + return "reposted post does not exist" + } + return "Repost: " + post.GetSummary() + "\n" + thread.RenderSummary() + } + str := "" + if post.title != "" { + str += "## [" + summaryOf(post.title, 80) + "](" + post.GetURL() + ")\n" + str += "\n" + } + str += post.GetSummary() + "\n" + str += "\\- " + displayAddressMD(post.creator) + "," + str += " [" + post.createdAt.Format("2006-01-02 3:04pm MST") + "](" + post.GetURL() + ")" + str += " \\[[x](" + post.GetDeleteFormURL() + ")]" + str += " (" + strconv.Itoa(post.replies.Size()) + " replies)" + str += " (" + strconv.Itoa(post.reposts.Size()) + " reposts)" + "\n" + return str +} + +func (post *Post) RenderPost(indent string, levels int) string { + if post == nil { + return "nil post" + } + str := "" + if post.title != "" { + str += indent + "# " + post.title + "\n" + str += indent + "\n" + } + str += indentBody(indent, post.body) + "\n" // TODO: indent body lines. + str += indent + "\\- " + displayAddressMD(post.creator) + ", " + str += "[" + post.createdAt.Format("2006-01-02 3:04pm (MST)") + "](" + post.GetURL() + ")" + str += " \\[[reply](" + post.GetReplyFormURL() + ")]" + if post.IsThread() { + str += " \\[[repost](" + post.GetRepostFormURL() + ")]" + } + str += " \\[[x](" + post.GetDeleteFormURL() + ")]\n" + if levels > 0 { + if post.replies.Size() > 0 { + post.replies.Iterate("", "", func(key string, value interface{}) bool { + str += indent + "\n" + str += value.(*Post).RenderPost(indent+"> ", levels-1) + return false + }) + } + } else { + if post.replies.Size() > 0 { + str += indent + "\n" + str += indent + "_[see all " + strconv.Itoa(post.replies.Size()) + " replies](" + post.GetURL() + ")_\n" + } + } + return str +} + +// render reply and link to context thread +func (post *Post) RenderInner() string { + if post.IsThread() { + panic("unexpected thread") + } + threadID := post.threadID + // replyID := post.id + parentID := post.parentID + str := "" + str += "_[see thread](" + post.board.GetURLFromThreadAndReplyID( + threadID, 0) + ")_\n\n" + thread := post.board.GetThread(post.threadID) + var parent *Post + if thread.id == parentID { + parent = thread + } else { + parent = thread.GetReply(parentID) + } + str += parent.RenderPost("", 0) + str += "\n" + str += post.RenderPost("> ", 5) + return str +} + +//---------------------------------------- +// Public facing functions + +func GetBoardIDFromName(name string) (BoardID, bool) { + boardI, exists := gBoardsByName.Get(name) + if !exists { + return 0, false + } + return boardI.(*Board).id, true +} + +func CreateBoard(name string) BoardID { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + bid := incGetBoardID() + caller := std.OriginCaller() + if usernameOf(caller) == "" { + panic("unauthorized") + } + url := "/r/demo/boards:" + name + board := newBoard(bid, url, name, caller) + bidkey := boardIDKey(bid) + gBoards.Set(bidkey, board) + gBoardsByName.Set(name, board) + return board.id +} + +func checkAnonFee() bool { + sent := std.OriginSend() + anonFeeCoin := std.NewCoin("ugnot", int64(gDefaultAnonFee)) + if len(sent) == 1 && sent[0].IsGTE(anonFeeCoin) { + return true + } + return false +} + +func CreateThread(bid BoardID, title string, body string) PostID { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + caller := std.OriginCaller() + if usernameOf(caller) == "" { + if !checkAnonFee() { + panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") + } + } + board := getBoard(bid) + if board == nil { + panic("board not exist") + } + thread := board.AddThread(caller, title, body) + return thread.id +} + +func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + caller := std.OriginCaller() + if usernameOf(caller) == "" { + if !checkAnonFee() { + panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") + } + } + board := getBoard(bid) + if board == nil { + panic("board not exist") + } + thread := board.GetThread(threadid) + if thread == nil { + panic("thread not exist") + } + if postid == threadid { + reply := thread.AddReply(caller, body) + return reply.id + } else { + post := thread.GetReply(postid) + reply := post.AddReply(caller, body) + return reply.id + } +} + +// If dstBoard is private, does not ping back. +// If board specified by bid is private, panics. +func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + caller := std.OriginCaller() + if usernameOf(caller) == "" { + // TODO: allow with gDefaultAnonFee payment. + if !checkAnonFee() { + panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") + } + } + board := getBoard(bid) + if board == nil { + panic("src board not exist") + } + if board.IsPrivate() { + panic("cannot repost from a private board") + } + dst := getBoard(dstBoardID) + if dst == nil { + panic("dst board not exist") + } + thread := board.GetThread(postid) + if thread == nil { + panic("thread not exist") + } + repost := thread.AddRepostTo(caller, title, body, dst) + return repost.id +} + +func DeletePost(bid BoardID, threadid, postid PostID, reason string) { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + caller := std.OriginCaller() + board := getBoard(bid) + if board == nil { + panic("board not exist") + } + thread := board.GetThread(threadid) + if thread == nil { + panic("thread not exist") + } + if postid == threadid { + // delete thread + if !thread.HasPermission(caller, DeletePermission) { + panic("unauthorized") + } + board.DeleteThread(threadid) + } else { + // delete thread's post + post := thread.GetReply(postid) + if post == nil { + panic("post not exist") + } + if !post.HasPermission(caller, DeletePermission) { + panic("unauthorized") + } + thread.DeletePost(postid) + } +} + +func EditPost(bid BoardID, threadid, postid PostID, title, body string) { + if !std.PreviousRealm().IsUser() { + panic("invalid non-user call") + } + caller := std.OriginCaller() + board := getBoard(bid) + if board == nil { + panic("board not exist") + } + thread := board.GetThread(threadid) + if thread == nil { + panic("thread not exist") + } + if postid == threadid { + // edit thread + if !thread.HasPermission(caller, EditPermission) { + panic("unauthorized") + } + thread.Update(title, body) + } else { + // edit thread's post + post := thread.GetReply(postid) + if post == nil { + panic("post not exist") + } + if !post.HasPermission(caller, EditPermission) { + panic("unauthorized") + } + post.Update(title, body) + } +} + +//---------------------------------------- +// Render functions + +func RenderBoard(bid BoardID) string { + board := getBoard(bid) + if board == nil { + return "missing board" + } + return board.RenderBoard() +} + +func Render(path string) string { + if path == "" { + str := "These are all the boards of this realm:\n\n" + gBoards.Iterate("", "", func(key string, value interface{}) bool { + board := value.(*Board) + str += " * [" + board.url + "](" + board.url + ")\n" + return false + }) + return str + } + parts := strings.Split(path, "/") + if len(parts) == 1 { + // /r/demo/boards:BOARD_NAME + name := parts[0] + boardI, exists := gBoardsByName.Get(name) + if !exists { + return "board does not exist: " + name + } + return boardI.(*Board).RenderBoard() + } else if len(parts) == 2 { + // /r/demo/boards:BOARD_NAME/THREAD_ID + name := parts[0] + boardI, exists := gBoardsByName.Get(name) + if !exists { + return "board does not exist: " + name + } + pid, err := strconv.Atoi(parts[1]) + if err != nil { + return "invalid thread id: " + parts[1] + } + board := boardI.(*Board) + thread := board.GetThread(PostID(pid)) + if thread == nil { + return "thread does not exist with id: " + parts[1] + } + return thread.RenderPost("", 5) + } else if len(parts) == 3 { + // /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID + name := parts[0] + boardI, exists := gBoardsByName.Get(name) + if !exists { + return "board does not exist: " + name + } + pid, err := strconv.Atoi(parts[1]) + if err != nil { + return "invalid thread id: " + parts[1] + } + board := boardI.(*Board) + thread := board.GetThread(PostID(pid)) + if thread == nil { + return "thread does not exist with id: " + parts[1] + } + rid, err := strconv.Atoi(parts[2]) + if err != nil { + return "invalid reply id: " + parts[2] + } + reply := thread.GetReply(PostID(rid)) + if reply == nil { + return "reply does not exist with id: " + parts[2] + } + return reply.RenderInner() + } else { + return "unrecognized path " + path + } +} + +type Permission string + +const ( + DeletePermission Permission = "role:delete" + EditPermission Permission = "role:edit" +) \ No newline at end of file diff --git a/gno.land/pkg/integration/testdata/issue_2763.txtar b/gno.land/pkg/integration/testdata/issue_2763.txtar new file mode 100644 index 00000000000..b3bc59804a5 --- /dev/null +++ b/gno.land/pkg/integration/testdata/issue_2763.txtar @@ -0,0 +1,39 @@ +gnoland start + +# add contract +# Note: addpkg does not add *_test.gno files, so it works as expected without +# causing any redeclaration issues. +gnokey maketx addpkg -pkgdir $WORK/foo -pkgpath gno.land/r/demo/foo -gas-fee 1000000ugnot -gas-wanted 16000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +-- foo/foo.gno -- +package foo + +func Add(a, b int) int { + return add2(a, b) +} + +func add2(a, b int) int { + return a + b +} + + +-- foo/foo_test.gno -- +package foo + +import ( + "testing" +) + +func TestFoo(t *testing.T) { + a := 1 + b := 2 + + if Add(a, b) != 3 { + t.Errorf("Add(%d, %d) = %d, want %d", a, b, Add(a, b), a+b) + } +} + +func add2(a, b int) int { + return a + b +} diff --git a/gno.land/pkg/integration/testdata/issue_3546.txtar b/gno.land/pkg/integration/testdata/issue_3546.txtar new file mode 100644 index 00000000000..6d0704df879 --- /dev/null +++ b/gno.land/pkg/integration/testdata/issue_3546.txtar @@ -0,0 +1,3 @@ +loadpkg gno.land/r/gov/dao/v2 + +gnoland start \ No newline at end of file diff --git a/gno.land/pkg/integration/testdata/moul_authz.txtar b/gno.land/pkg/integration/testdata/moul_authz.txtar new file mode 100644 index 00000000000..d0da3f535e8 --- /dev/null +++ b/gno.land/pkg/integration/testdata/moul_authz.txtar @@ -0,0 +1,82 @@ +loadpkg gno.land/p/moul/authz +loadpkg gno.land/r/testing/admin $WORK/admin +loadpkg gno.land/r/testing/resource $WORK/resource + +adduserfrom alice 'smooth crawl poverty trumpet glare useful curtain annual pluck lunar example merge ready forum better verb rescue rule mechanic dynamic drift bench release weekend' +stdout 'g1rfznvu6qfa0sc76cplk5wpqexvefqccjunady0' + +gnoland start + +gnokey maketx call -pkgpath gno.land/r/testing/resource -func Edit -args edited -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test alice + +gnokey maketx call -pkgpath gno.land/r/testing/admin -func ExecuteAction -args 0 -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test alice + +gnokey maketx call -pkgpath gno.land/r/testing/resource -func Value -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test alice +stdout 'edited' + + +-- admin/gno.mod -- +module gno.land/r/testing/admin + +-- admin/admin.gno -- +package admin + +import ( + "std" + "errors" + "gno.land/p/moul/authz" +) + +type prop struct { + title string + action authz.PrivilegedAction +} + +var props []*prop + +func HandlePrivilegedAction(title string, action authz.PrivilegedAction) error { + if std.PreviousRealm().PkgPath() != "gno.land/r/testing/resource" { + return errors.New("unauthorized proposer") + } + props = append(props, &prop{title: title, action: action}) + return nil +} + +func ExecuteAction(index int) { + if std.PreviousRealm().Address() != "g1rfznvu6qfa0sc76cplk5wpqexvefqccjunady0" { + panic(errors.New("not alice")) + } + if err := props[index].action(); err != nil { + panic(err) + } +} + +-- resource/gno.mod -- +module gno.land/r/testing/resource + +-- resource/resource.gno -- +package resource + +import ( + "gno.land/p/moul/authz" + "gno.land/r/testing/admin" +) + +var a *authz.Authorizer = authz.NewWithAuthority(authz.NewContractAuthority("gno.land/r/testing/admin", admin.HandlePrivilegedAction)) + +var value = "init" + +func Value() string { + return value +} + +func Edit(newValue string) { + doEdit := func() error { + value = newValue + return nil + } + if err := a.Do("Edit", doEdit); err != nil { + panic(err) + } +} + diff --git a/gno.land/pkg/integration/testdata/params.txtar b/gno.land/pkg/integration/testdata/params.txtar index 30363aa6369..40fa62c53e7 100644 --- a/gno.land/pkg/integration/testdata/params.txtar +++ b/gno.land/pkg/integration/testdata/params.txtar @@ -3,63 +3,83 @@ gnoland start # query before adding the package -gnokey query params/vm/gno.land/r/sys/setter.foo.string +gnokey query params/vm:gno.land/r/myrealm:foo stdout 'data: $' -gnokey query params/vm/gno.land/r/sys/setter.bar.bool +gnokey query params/vm:gno.land/r/myrealm:bar stdout 'data: $' -gnokey query params/vm/gno.land/r/sys/setter.baz.int64 +gnokey query params/vm:gno.land/r/myrealm:baz stdout 'data: $' -gnokey maketx addpkg -pkgdir $WORK/setter -pkgpath gno.land/r/sys/setter -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 +# add params to gno.land/r/myrealm +gnokey maketx addpkg -pkgdir $WORK/params -pkgpath gno.land/r/myrealm -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 # query after adding the package, but before setting values -gnokey query params/vm/gno.land/r/sys/setter.foo.string +gnokey query params/vm:gno.land/r/myrealm:foo stdout 'data: $' -gnokey query params/vm/gno.land/r/sys/setter.bar.bool +gnokey query params/vm:gno.land/r/myrealm:bar stdout 'data: $' -gnokey query params/vm/gno.land/r/sys/setter.baz.int64 +gnokey query params/vm:gno.land/r/myrealm:baz stdout 'data: $' - -# set foo (string) -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetFoo -args foo1 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.foo.string +## set foo (string) +gnokey maketx call -pkgpath gno.land/r/myrealm -func SetFoo -args foo1 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm:gno.land/r/myrealm:foo stdout 'data: "foo1"' # override foo -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetFoo -args foo2 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.foo.string +gnokey maketx call -pkgpath gno.land/r/myrealm -func SetFoo -args foo2 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm:gno.land/r/myrealm:foo stdout 'data: "foo2"' # set bar (bool) -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetBar -args true -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.bar.bool +gnokey maketx call -pkgpath gno.land/r/myrealm -func SetBar -args true -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm:gno.land/r/myrealm:bar stdout 'data: true' -# override bar -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetBar -args false -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.bar.bool +# override bar +gnokey maketx call -pkgpath gno.land/r/myrealm -func SetBar -args false -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm:gno.land/r/myrealm:bar stdout 'data: false' -# set baz (bool) -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetBaz -args 1337 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.baz.int64 +# set baz (int64) +gnokey maketx call -pkgpath gno.land/r/myrealm -func SetBaz -args 1337 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm:gno.land/r/myrealm:baz stdout 'data: "1337"' # override baz -gnokey maketx call -pkgpath gno.land/r/sys/setter -func SetBaz -args 31337 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 -gnokey query params/vm/gno.land/r/sys/setter.baz.int64 -stdout 'data: "31337"' +gnokey maketx call -pkgpath gno.land/r/myrealm -func SetBaz -args -31337 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm:gno.land/r/myrealm:baz +stdout 'data: "-31337"' + +# It is impossible to call std.SetParamXXX with a param key in the : format (e.g. "bank:p:lockTransfer") because it is an invalid key. +! gnokey maketx call -pkgpath gno.land/r/myrealm -func SetLockTransfer -args ugnot -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +stderr 'Data: invalid param key: bank:p:lockTransfer' + +# . is invalid (e.g., "bank.lockTransfer") +! gnokey maketx call -pkgpath gno.land/r/myrealm -func SetInvalidString -args ugnot -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +stderr 'Data: invalid param key: bank:p:lockTransfer' --- setter/setter.gno -- -package setter +# . (e.g., "bank_lockTransfer") is a valid name. +gnokey maketx call -pkgpath gno.land/r/myrealm -func SetValidString -args ugnot -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/vm:gno.land/r/myrealm:bank_lockTransfer +stdout 'data: "ugnot"' + + +-- params/setter.gno -- +package params import ( "std" ) -func SetFoo(newFoo string) { std.SetParamString("foo.string", newFoo) } -func SetBar(newBar bool) { std.SetParamBool("bar.bool", newBar) } -func SetBaz(newBaz int64) { std.SetParamInt64("baz.int64", newBaz) } +func SetFoo(newFoo string) { std.SetParamString("foo", newFoo) } +func SetBar(newBar bool) { std.SetParamBool("bar", newBar) } +func SetBaz(newBaz int64) { std.SetParamInt64("baz", newBaz) } +func SetUint64(newBaz uint64) { std.SetParamUint64("baz", newBaz) } +func SetBytes() { std.SetParamBytes("baz", []byte{255,255}) } + +func SetLockTransfer(denom string) { std.SetParamString("bank:p:lockTransfer", denom) } +func SetInvalidString(denom string) { std.SetParamString("bank:p:lockTransfer", denom) } +func SetValidString(denom string) { std.SetParamString("bank_lockTransfer", denom) } diff --git a/gno.land/pkg/integration/testdata/params_sysparams1.txtar b/gno.land/pkg/integration/testdata/params_sysparams1.txtar new file mode 100644 index 00000000000..6800cf18ccb --- /dev/null +++ b/gno.land/pkg/integration/testdata/params_sysparams1.txtar @@ -0,0 +1,40 @@ +# tests for + +gnoland start + +# Test sys/params.SetSysParamXXX when called from gno.land/r/sys/params + +gnokey maketx addpkg -pkgdir $WORK/params -pkgpath gno.land/r/sys/params -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +## before set lock transfer +gnokey query params/bank:p:restricted_denoms +stdout 'data: \[\]\n' + +## lock transfer +gnokey maketx call -pkgpath gno.land/r/sys/params -func SetLockTransfer -args "ugnot" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 + +## query bank module +gnokey query params/bank:p:restricted_denoms +stdout 'data: \["ugnot"\]\n' + +## query vm module +gnokey query params/vm:p:sysnames_pkgpath +stdout 'data: "gno.land/r/sys/names"\n' +gnokey query params/vm:p:chain_domain +stdout 'data: "gno.land"\n' + +## query auth module +gnokey query params/auth:p:max_memo_bytes +stdout 'data: "65536"\n' +gnokey query params/auth:p:tx_sig_limit +stdout 'data: "7"\n' + +-- params/setter.gno -- +package params + +import ( + "sys/params" +) + +// This should succeed if it is called from gno.land/r/sys/params +func SetLockTransfer(denom string) { params.SetSysParamStrings("bank","p", "restricted_denoms", []string{denom}) } diff --git a/gno.land/pkg/integration/testdata/params_sysparams2.txtar b/gno.land/pkg/integration/testdata/params_sysparams2.txtar new file mode 100644 index 00000000000..054c2c5e5d3 --- /dev/null +++ b/gno.land/pkg/integration/testdata/params_sysparams2.txtar @@ -0,0 +1,97 @@ +# test set sysparam (ParamKeeper) + +gnoland start + +# ---- 1 Test sys/params.SetSysParamXXX when called from gno.land/r/sys/params + +gnokey maketx addpkg -pkgdir $WORK/params -pkgpath gno.land/r/sys/params -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +## lock transfer +gnokey maketx call -pkgpath gno.land/r/sys/params -func SetLockTransfer -args "ugnot" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/bank:p:restricted_denoms +stdout 'data: \["ugnot"\]' + +# unlock transfer +gnokey maketx call -pkgpath gno.land/r/sys/params -func SetLockTransfer -args "" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey query params/bank:p:restricted_denoms +stdout 'data: \[""\]' + +# set non-existing module param +gnokey maketx call -pkgpath gno.land/r/sys/params -func SetBankArbitrary -args "foo" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +#stderr 'invalid bank parameter name: newkey' + +gnokey query params/bank:p:newkey +stdout 'data: "foo"' + +# set invalid key +! gnokey maketx call -pkgpath gno.land/r/sys/params -func SetInvalidKey -args "ugnot" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +stderr 'invalid param name: bank:restricted_denoms' + +gnokey query params/bank:p:restricted_denoms +stdout 'data: \[""\]' # still the same. + +gnokey query params/bank:restricted_denoms +stdout 'data: \n' + + +# ---- 2 Test sys/params.SetSysParamXXX when called outside of gno.land/r/sys/params +gnokey maketx addpkg -pkgdir $WORK/params -pkgpath gno.land/r/myrealm -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +## can not call SetSysParamXXX out side of gno.land/r/params +! gnokey maketx call -pkgpath gno.land/r/myrealm -func SetSysParamString -args "foo" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +stderr 'Data: "sys/params" can only be used from "gno.land/r/sys/params"' # XXX can only be *imported* from... enforce import rule + +gnokey query params/bank:p:foo +stdout 'data: \n' + +! gnokey maketx call -pkgpath gno.land/r/myrealm -func SetSysParamBool -args true -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +stderr 'Data: "sys/params" can only be used from "gno.land/r/sys/params"' # XXX can only be *imported* from... enforce import rule + +gnokey query params/bank:p:bar +stdout 'data: \n' + +! gnokey maketx call -pkgpath gno.land/r/myrealm -func SetSysParamInt64 -args -100 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +stderr 'Data: "sys/params" can only be used from "gno.land/r/sys/params"' # XXX can only be *imported* from... enforce import rule + +gnokey query params/bank:p:baz +stdout 'data: \n' + +! gnokey maketx call -pkgpath gno.land/r/myrealm -func SetSysParamUint64 -args 100 -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +stderr 'Data: "sys/params" can only be used from "gno.land/r/sys/params"' # XXX can only be *imported* from... enforce import rule + +gnokey query params/bank:p:baz +stdout 'data: \n' + +! gnokey maketx call -pkgpath gno.land/r/myrealm -func SetSysParamBytes -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +stderr 'Data: "sys/params" can only be used from "gno.land/r/sys/params"' # XXX can only be *imported* from... enforce import rule + +gnokey query params/bank:p:baz +stdout 'data: \n' + + +-- params/setter.gno -- +package params + +import ( + "sys/params" +) + +// This should succeed if it is called from gno.land/r/sys/params +func SetLockTransfer(denom string) { params.SetSysParamStrings("bank","p", "restricted_denoms", []string{denom}) } + +// # XXX test setting the wrong type, like SetSysParamString() instead of Strings(), and make it error for special case of "p". +// # func SetLockTransfer(denom string) { params.SetSysParamString("bank","p", "restricted_denoms", denom) } + +// This should fail because the parameter does not exist +func SetBankArbitrary(value string) { params.SetSysParamString("bank","p", "newkey", value) } + +// SetSysParamXXX must be called from gno.land/r/sys/params; otherwise it panics + +// This should fail because the key "bank:restricted_denoms" is not valid +func SetInvalidKey(denom string) { params.SetSysParamString("bank", "p", "bank:restricted_denoms", "ugnot") } + +func SetSysParamString(s string) { params.SetSysParamString("bank","p", "foo", s) } +func SetSysParamBool(b bool) { params.SetSysParamBool("bank","p", "bar", b) } +func SetSysParamInt64(i int64) { params.SetSysParamInt64("bank","p", "baz", i) } +func SetSysParamUint64(u uint64) { params.SetSysParamUint64("bank","p", "baz", u) } +func SetSysParamBytes() { params.SetSysParamBytes("bank","p", "baz", []byte{255,255}) } diff --git a/gno.land/pkg/integration/testdata/prevrealm.txtar b/gno.land/pkg/integration/testdata/prevrealm.txtar index 31f0ca336ba..73340d9b44d 100644 --- a/gno.land/pkg/integration/testdata/prevrealm.txtar +++ b/gno.land/pkg/integration/testdata/prevrealm.txtar @@ -1,4 +1,4 @@ -# This tests ensure the consistency of the std.PrevRealm function, in the +# This tests ensure the consistency of the std.PreviousRealm function, in the # following situations: # # @@ -16,8 +16,8 @@ # | 10 | | | myrlm.B() | r/foo | # | 11 | | through /p/demo/bar | bar.A() | user address | # | 12 | | | bar.B() | user address | -# | 13 | MsgCall | wallet direct | std.PrevRealm() | user address | -# | 14 | MsgRun | wallet direct | std.PrevRealm() | user address | +# | 13 | MsgCall | wallet direct | std.PreviousRealm() | user address | +# | 14 | MsgRun | wallet direct | std.PreviousRealm() | user address | # Init ## deploy myrlm @@ -82,11 +82,11 @@ stdout ${test1_user_addr} gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno stdout ${test1_user_addr} -## 13. MsgCall -> std.PrevRealm(): user address -## gnokey maketx call -pkgpath std -func PrevRealm -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 +## 13. MsgCall -> std.PreviousRealm(): user address +## gnokey maketx call -pkgpath std -func PreviousRealm -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 ## stdout ${test1_user_addr} -## 14. MsgRun -> std.PrevRealm(): user address +## 14. MsgRun -> std.PreviousRealm(): user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno stdout ${test1_user_addr} @@ -96,7 +96,7 @@ package myrlm import "std" func A() string { - return std.PrevRealm().Addr().String() + return std.PreviousRealm().Address().String() } func B() string { @@ -120,7 +120,7 @@ package bar import "std" func A() string { - return std.PrevRealm().Addr().String() + return std.PreviousRealm().Address().String() } func B() string { @@ -180,5 +180,5 @@ package main import "std" func main() { - println(std.PrevRealm().Addr().String()) + println(std.PreviousRealm().Address().String()) } diff --git a/gno.land/pkg/integration/testdata/ptr_mapkey.txtar b/gno.land/pkg/integration/testdata/ptr_mapkey.txtar new file mode 100644 index 00000000000..77f0f845c12 --- /dev/null +++ b/gno.land/pkg/integration/testdata/ptr_mapkey.txtar @@ -0,0 +1,31 @@ +gnoland start + +# add contract +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/ptrmap -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/demo/ptrmap -func AddToMap -args 5 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout OK! + +gnokey maketx call -pkgpath gno.land/r/demo/ptrmap -func GetFromMap -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(5 int)' +stdout OK! + +-- gno.mod -- +module gno.land/r/demo/ptrmap + +-- realm.gno -- +package ptrmap + +var ( + m = map[*int]int{} + i = new(int) +) + +func AddToMap(value int) { + m[i] = value +} + +func GetFromMap() int { + return m[i] +} \ No newline at end of file diff --git a/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar b/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar index a55604267ae..a95f7e6f93d 100644 --- a/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar +++ b/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar @@ -79,12 +79,12 @@ import ( ) func Mint(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.IssueCoin(addr, std.CurrentRealm().CoinDenom(denom), amount) } func Burn(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.RemoveCoin(addr, std.CurrentRealm().CoinDenom(denom), amount) } @@ -97,12 +97,12 @@ import ( ) func Mint(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.IssueCoin(addr, std.CurrentRealm().CoinDenom(denom), amount) } func Burn(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.RemoveCoin(addr, std.CurrentRealm().CoinDenom(denom), amount) } @@ -114,11 +114,11 @@ import ( ) func Mint(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.IssueCoin(addr, denom, amount) } func Burn(addr std.Address, denom string, amount int64) { - banker := std.GetBanker(std.BankerTypeRealmIssue) + banker := std.NewBanker(std.BankerTypeRealmIssue) banker.RemoveCoin(addr, denom, amount) } diff --git a/gno.land/pkg/integration/testdata/restart_missing_type.txtar b/gno.land/pkg/integration/testdata/restart_missing_type.txtar index cc8ed702734..ffb47a4ac2d 100644 --- a/gno.land/pkg/integration/testdata/restart_missing_type.txtar +++ b/gno.land/pkg/integration/testdata/restart_missing_type.txtar @@ -9,15 +9,15 @@ stdout 'g1lmgyf29g6zqgpln5pq05zzt7qkz2wga7xgagv4' loadpkg gno.land/p/demo/avl gnoland start -gnokey sign -tx-path $WORK/tx1.tx -chainid tendermint_test -account-sequence 0 -account-number 57 user1 +gnokey sign -tx-path $WORK/tx1.tx -chainid tendermint_test -account-sequence 0 -account-number 58 user1 ! gnokey broadcast $WORK/tx1.tx stderr 'out of gas' -gnokey sign -tx-path $WORK/tx2.tx -chainid tendermint_test -account-sequence 1 -account-number 57 user1 +gnokey sign -tx-path $WORK/tx2.tx -chainid tendermint_test -account-sequence 1 -account-number 58 user1 gnokey broadcast $WORK/tx2.tx stdout 'OK!' -gnokey sign -tx-path $WORK/tx3.tx -chainid tendermint_test -account-sequence 2 -account-number 57 user1 +gnokey sign -tx-path $WORK/tx3.tx -chainid tendermint_test -account-sequence 2 -account-number 58 user1 gnokey broadcast $WORK/tx3.tx stdout 'OK!' diff --git a/gno.land/pkg/integration/testdata/simulate_gas.txtar b/gno.land/pkg/integration/testdata/simulate_gas.txtar index 0dcb9ba424b..4fe8a690adf 100644 --- a/gno.land/pkg/integration/testdata/simulate_gas.txtar +++ b/gno.land/pkg/integration/testdata/simulate_gas.txtar @@ -6,11 +6,11 @@ gnoland start # simulate only gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -stdout 'GAS USED: 99371' +stdout 'GAS USED: 99587' # simulate skip gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -stdout 'GAS USED: 99371' # same as simulate only +stdout 'GAS USED: 99587' # same as simulate only -- package/package.gno -- diff --git a/gno.land/pkg/integration/testdata/transfer_lock.txtar b/gno.land/pkg/integration/testdata/transfer_lock.txtar new file mode 100644 index 00000000000..146965290ec --- /dev/null +++ b/gno.land/pkg/integration/testdata/transfer_lock.txtar @@ -0,0 +1,55 @@ +## It tests locking token transfers while allowing the payment of gas fees. + +## locking transfer applies to regular accounts +adduser regular1 + + +loadpkg gno.land/r/demo/wugnot +loadpkg gno.land/r/demo/echo + +## start a new node. +## The -lock-transfer flag is intended for integration testing purposes +## and is not a valid application flag for gnoland. + +gnoland start -lock-transfer + +## test1 is the DefaultAccount in the integration test. To ensure that the unrestricted account can send tokens even when token transfers are locked, +## we included it in the unrestricted account list in the genesis state. By default, the unrestricted account list is empty. +gnokey maketx send -send "9999999ugnot" -to $regular1_user_addr -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 + +stdout 'OK!' + +## Restricted simple token transfer +! gnokey maketx send -send "9999999ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test regular1 +stderr 'restricted token transfer error' + +## Restricted token transfer by calling a realm deposit function. +! gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -gas-fee 1000000ugnot -send "10000ugnot" -gas-wanted 2000000 -broadcast -chainid=tendermint_test regular1 +stderr 'restricted token transfer error' + + +## Restricted token transfer with depositing to a realm package while adding a package. +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/bank -deposit "1000ugnot" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test regular1 +stderr 'restricted token transfer error' + +## paying gas fees to add a package is acceptable. +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/bank -gas-fee 1000000ugnot -gas-wanted 12500000 -broadcast -chainid=tendermint_test regular1 +stdout 'OK!' + +## paying gas fees to call a realm function is acceptable. +gnokey maketx call -pkgpath gno.land/r/demo/echo -func Render -args "Hello!" -gas-fee 1000000ugnot -gas-wanted 12500000 -broadcast -chainid=tendermint_test regular1 +stdout 'Hello!' + +-- bank.gno -- +package bank +import ( +"std" +) +func Withdraw(denom string, amt int64) string{ + caller := std.OriginCaller() + coin := std.Coins{{denom, amt}} + banker := std.NewBanker(std.BankerTypeOriginSend) + pkgaddr := std.OriginPkgAddress() + banker.SendCoins(pkgaddr, caller, coin) + return "Withdrawn!" +} diff --git a/gno.land/pkg/integration/testdata/transfer_unlock.txtar b/gno.land/pkg/integration/testdata/transfer_unlock.txtar new file mode 100644 index 00000000000..6a5ac729722 --- /dev/null +++ b/gno.land/pkg/integration/testdata/transfer_unlock.txtar @@ -0,0 +1,65 @@ +## It tests unlocking token transfers through GovDAO voting +loadpkg gno.land/r/sys/params +loadpkg gno.land/r/gov/dao/v2 + +patchpkg "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + +adduser regular1 + +## The -lock-transfer flag is not a Gnoland service flag; it is a flag for the txtar setting. +gnoland start -lock-transfer + +## test1 is the DefaultAccount in the integration test. To ensure that the unrestricted account can send tokens even when token transfers are locked, +## we included it in the unrestricted account list in the genesis state. By default, the unrestricted account list is empty. +gnokey maketx send -send "9999999ugnot" -to $regular1_user_addr -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 + +stdout 'OK!' + +## Restricted simple token transfer for a regular account +! gnokey maketx send -send "100ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test regular1 + +stderr 'restricted token transfer error' + +## Submit a proposal to unlock the transfer. When token transfer is locked, only the predefined unrestricted account test1 in the genesis state can +## pay the fee and submit a proposal to unlock the transfer. +gnokey maketx call -pkgpath gno.land/r/sys/params -func ProposeUnlockTransfer -send 250000000ugnot -gas-fee 1ugnot -gas-wanted 9500000 -broadcast -chainid=tendermint_test test1 + +stdout '(0 uint64)' + + +## Vote unlock proposal with unrestricted account test1 +gnokey maketx run -gas-fee 1ugnot -gas-wanted 95000000 -broadcast -chainid=tendermint_test test1 $WORK/run/vote_proposal.gno + +stdout 'OK!' + +## Execute unlock proposal with unrestricted account test1 +gnokey maketx run -gas-fee 1ugnot -gas-wanted 95000000 -broadcast -chainid=tendermint_test test1 $WORK/run/exec_proposal.gno + +stdout 'OK!' + +## Restricted transfer is unlocked, allowing simple token transfers for regular accounts. +gnokey maketx send -send "100ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test regular1 + +stdout 'OK!' + +-- run/vote_proposal.gno -- +package main + +import ( + govdao "gno.land/r/gov/dao/v2" +) + +func main() { + govdao.GovDAO.VoteOnProposal(0,"YES") +} + +-- run/exec_proposal.gno -- +package main + +import ( + govdao "gno.land/r/gov/dao/v2" +) + +func main() { + govdao.GovDAO.ExecuteProposal(0) +} diff --git a/gno.land/pkg/integration/testdata_test.go b/gno.land/pkg/integration/testdata_test.go index ba4d5176df1..fce0d8e3bfb 100644 --- a/gno.land/pkg/integration/testdata_test.go +++ b/gno.land/pkg/integration/testdata_test.go @@ -14,7 +14,7 @@ func TestTestdata(t *testing.T) { t.Parallel() flagInMemoryTS, _ := strconv.ParseBool(os.Getenv("INMEMORY_TS")) - flagNoSeqTS, _ := strconv.ParseBool(os.Getenv("NO_SEQ_TS")) + flagSeqTS, _ := strconv.ParseBool(os.Getenv("SEQ_TS")) p := gno_integration.NewTestingParams(t, "testdata") @@ -44,7 +44,7 @@ func TestTestdata(t *testing.T) { return nil } - if flagInMemoryTS && !flagNoSeqTS { + if flagInMemoryTS || flagSeqTS { testscript.RunT(tSeqShim{t}, p) } else { testscript.Run(t, p) diff --git a/gno.land/pkg/integration/testscript_gnoland.go b/gno.land/pkg/integration/testscript_gnoland.go index 1531b83dfef..21e3f88de39 100644 --- a/gno.land/pkg/integration/testscript_gnoland.go +++ b/gno.land/pkg/integration/testscript_gnoland.go @@ -183,19 +183,17 @@ func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { env.Setenv("SID", sid) } - balanceFile := LoadDefaultGenesisBalanceFile(t, gnoRootDir) - genesisParamFile := LoadDefaultGenesisParamFile(t, gnoRootDir) - // Track new user balances added via the `adduser` // command and packages added with the `loadpkg` command. // This genesis will be use when node is started. - genesis := &gnoland.GnoGenesisState{ - Balances: balanceFile, - Params: genesisParamFile, - Txs: []gnoland.TxWithMetadata{}, - } - env.Values[envKeyGenesis] = genesis + genesis := gnoland.DefaultGenState() + genesis.Balances = LoadDefaultGenesisBalanceFile(t, gnoRootDir) + genesis.Auth.Params.InitialGasPrice = std.GasPrice{Gas: 0, Price: std.Coin{Amount: 0, Denom: "ugnot"}} + genesis.Txs = []gnoland.TxWithMetadata{} + LoadDefaultGenesisParamFile(t, gnoRootDir, &genesis) + + env.Values[envKeyGenesis] = &genesis env.Values[envKeyPkgsLoader] = NewPkgsLoader() env.Setenv("GNOROOT", gnoRootDir) @@ -273,6 +271,7 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) fun // directly or use the config command for this. fs := flag.NewFlagSet("start", flag.ContinueOnError) nonVal := fs.Bool("non-validator", false, "set up node as a non-validator") + lockTransfer := fs.Bool("lock-transfer", false, "lock transfer ugnot") if err := fs.Parse(cmdargs); err != nil { ts.Fatalf("unable to parse `gnoland start` flags: %s", err) } @@ -285,10 +284,16 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) fun } cfg := TestingMinimalNodeConfig(gnoRootDir) - genesis := ts.Value(envKeyGenesis).(*gnoland.GnoGenesisState) - genesis.Txs = append(pkgsTxs, genesis.Txs...) + tsGenesis := ts.Value(envKeyGenesis).(*gnoland.GnoGenesisState) + genesis := cfg.Genesis.AppState.(gnoland.GnoGenesisState) + genesis.Txs = append(genesis.Txs, append(pkgsTxs, tsGenesis.Txs...)...) + genesis.Balances = append(genesis.Balances, tsGenesis.Balances...) + if *lockTransfer { + genesis.Bank.Params.RestrictedDenoms = []string{"ugnot"} + } + genesis.VM.RealmParams = append(genesis.VM.RealmParams, tsGenesis.VM.RealmParams...) - cfg.Genesis.AppState = *genesis + cfg.Genesis.AppState = genesis if *nonVal { pv := gnoland.NewMockedPrivValidator() cfg.Genesis.Validators = []bft.GenesisValidator{ @@ -308,6 +313,7 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) fun priv := ts.Value(envKeyPrivValKey).(ed25519.PrivKeyEd25519) nodep := setupNode(ts, ctx, &ProcessNodeConfig{ ValidatorKey: priv, + Verbose: false, DBDir: dbdir, RootDir: gnoRootDir, TMConfig: cfg.TMConfig, diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 00b2be585c6..2d7f754203e 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -106,11 +106,12 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { } } } + + memPkg.Name = "main" if memPkg.IsEmpty() { panic(fmt.Sprintf("found an empty package %q", memPkg.Path)) } - memPkg.Name = "main" // Set to empty; this will be automatically set by the VM keeper. memPkg.Path = "" diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 161e459873d..925025e29f1 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -1,8 +1,12 @@ package vm import ( + "fmt" + "strings" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/gnolang/gno/tm2/pkg/sdk/params" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -59,22 +63,66 @@ func (bnk *SDKBanker) RemoveCoin(b32addr crypto.Bech32Address, denom string, amo // ---------------------------------------- // SDKParams +// This implements ParamsInterface, +// which is available as ExecContext.Params. +// Access to SDKParams gives access to all parameters. +// Users must write code to limit access as appropriate. + type SDKParams struct { - vmk *VMKeeper + pmk params.ParamsKeeper ctx sdk.Context } -func NewSDKParams(vmk *VMKeeper, ctx sdk.Context) *SDKParams { +func NewSDKParams(pmk params.ParamsKeeper, ctx sdk.Context) *SDKParams { return &SDKParams{ - vmk: vmk, + pmk: pmk, ctx: ctx, } } -func (prm *SDKParams) SetString(key, value string) { prm.vmk.prmk.SetString(prm.ctx, key, value) } -func (prm *SDKParams) SetBool(key string, value bool) { prm.vmk.prmk.SetBool(prm.ctx, key, value) } -func (prm *SDKParams) SetInt64(key string, value int64) { prm.vmk.prmk.SetInt64(prm.ctx, key, value) } +// The key has the format :(:)?. +func (prm *SDKParams) SetString(key string, value string) { + prm.willSetKeeperParams(prm.ctx, key, value) + prm.pmk.SetString(prm.ctx, key, value) +} + +func (prm *SDKParams) SetBool(key string, value bool) { + prm.willSetKeeperParams(prm.ctx, key, value) + prm.pmk.SetBool(prm.ctx, key, value) +} + +func (prm *SDKParams) SetInt64(key string, value int64) { + prm.willSetKeeperParams(prm.ctx, key, value) + prm.pmk.SetInt64(prm.ctx, key, value) +} + func (prm *SDKParams) SetUint64(key string, value uint64) { - prm.vmk.prmk.SetUint64(prm.ctx, key, value) + prm.willSetKeeperParams(prm.ctx, key, value) + prm.pmk.SetUint64(prm.ctx, key, value) +} + +func (prm *SDKParams) SetBytes(key string, value []byte) { + prm.willSetKeeperParams(prm.ctx, key, value) + prm.pmk.SetBytes(prm.ctx, key, value) +} + +func (prm *SDKParams) SetStrings(key string, value []string) { + prm.willSetKeeperParams(prm.ctx, key, value) + prm.pmk.SetStrings(prm.ctx, key, value) +} + +func (prm *SDKParams) willSetKeeperParams(ctx sdk.Context, key string, value interface{}) { + parts := strings.Split(key, ":") + if len(parts) == 0 { + panic(fmt.Sprintf("SDKParams encountered invalid param key format: %s", key)) + } + mname := parts[0] + if !prm.pmk.IsRegistered(mname) { + panic(fmt.Sprintf("module name <%s> not registered", mname)) + } + kpr := prm.pmk.GetRegisteredKeeper(mname) + if kpr != nil { + subkey := key[len(mname)+1:] + kpr.WillSetParam(prm.ctx, subkey, value) + } } -func (prm *SDKParams) SetBytes(key string, value []byte) { prm.vmk.prmk.SetBytes(prm.ctx, key, value) } diff --git a/gno.land/pkg/sdk/vm/builtins_test.go b/gno.land/pkg/sdk/vm/builtins_test.go new file mode 100644 index 00000000000..ab062beef60 --- /dev/null +++ b/gno.land/pkg/sdk/vm/builtins_test.go @@ -0,0 +1,67 @@ +package vm + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParamsKeeper(t *testing.T) { + env := setupTestEnv() + params := NewSDKParams(env.vmk.prmk, env.ctx) + + testCases := []struct { + name string + setFunc func() + expectedMsg string + }{ + { + name: "SetString should panic", + setFunc: func() { + params.SetString("foo:name", "foo") + }, + expectedMsg: `module name not registered`, + }, + { + name: "SetString should panic (with realm)", + setFunc: func() { + params.SetString("foo:gno.land/r/user/repo:name", "foo") + }, + expectedMsg: `module name not registered`, + }, + { + name: "SetBool should panic", + setFunc: func() { + params.SetBool("foo:name", true) + }, + expectedMsg: `module name not registered`, + }, + { + name: "SetInt64 should panic", + setFunc: func() { + params.SetInt64("foo:name", -100) + }, + expectedMsg: `module name not registered`, + }, + { + name: "SetUint64 should panic", + setFunc: func() { + params.SetUint64("foo:name", 100) + }, + expectedMsg: `module name not registered`, + }, + { + name: "SetBytes should panic", + setFunc: func() { + params.SetBytes("foo:name", []byte("foo")) + }, + expectedMsg: `module name not registered`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + require.PanicsWithValue(t, tc.expectedMsg, tc.setFunc, "The panic message did not match the expected value") + }) + } +} diff --git a/gno.land/pkg/sdk/vm/common_test.go b/gno.land/pkg/sdk/vm/common_test.go index 10402f31f64..f48a9777c48 100644 --- a/gno.land/pkg/sdk/vm/common_test.go +++ b/gno.land/pkg/sdk/vm/common_test.go @@ -11,7 +11,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/sdk" authm "github.com/gnolang/gno/tm2/pkg/sdk/auth" bankm "github.com/gnolang/gno/tm2/pkg/sdk/bank" - paramsm "github.com/gnolang/gno/tm2/pkg/sdk/params" + pm "github.com/gnolang/gno/tm2/pkg/sdk/params" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" "github.com/gnolang/gno/tm2/pkg/store/dbadapter" @@ -19,11 +19,12 @@ import ( ) type testEnv struct { - ctx sdk.Context - vmk *VMKeeper - bank bankm.BankKeeper - acck authm.AccountKeeper - vmh vmHandler + ctx sdk.Context + vmk *VMKeeper + bankk bankm.BankKeeper + acck authm.AccountKeeper + prmk pm.ParamsKeeper + vmh vmHandler } func setupTestEnv() testEnv { @@ -47,11 +48,15 @@ func _setupTestEnv(cacheStdlibs bool) testEnv { ms.LoadLatestVersion() ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{ChainID: "test-chain-id"}, log.NewNoopLogger()) - prmk := paramsm.NewParamsKeeper(iavlCapKey, "params") - acck := authm.NewAccountKeeper(iavlCapKey, prmk, std.ProtoBaseAccount) - bank := bankm.NewBankKeeper(acck) - vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, prmk) + prmk := pm.NewParamsKeeper(iavlCapKey) + acck := authm.NewAccountKeeper(iavlCapKey, prmk.ForModule(authm.ModuleName), std.ProtoBaseAccount) + bankk := bankm.NewBankKeeper(acck, prmk.ForModule(bankm.ModuleName)) + vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bankk, prmk) + + prmk.Register(authm.ModuleName, acck) + prmk.Register(bankm.ModuleName, bankk) + prmk.Register(ModuleName, vmk) mcw := ms.MultiCacheWrap() vmk.Initialize(log.NewNoopLogger(), mcw) @@ -66,5 +71,5 @@ func _setupTestEnv(cacheStdlibs bool) testEnv { mcw.MultiWrite() vmh := NewHandler(vmk) - return testEnv{ctx: ctx, vmk: vmk, bank: bank, acck: acck, vmh: vmh} + return testEnv{ctx: ctx, vmk: vmk, bankk: bankk, acck: acck, prmk: prmk, vmh: vmh} } diff --git a/gno.land/pkg/sdk/vm/convert.go b/gno.land/pkg/sdk/vm/convert.go index dbaabcfbc4b..508953731d8 100644 --- a/gno.land/pkg/sdk/vm/convert.go +++ b/gno.land/pkg/sdk/vm/convert.go @@ -50,7 +50,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { "error parsing int %q: %v", arg, err)) } - tv.SetInt(int(i64)) + tv.SetInt(i64) return case gno.Int8Type: assertNoPlusPrefix(arg) @@ -100,7 +100,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { "error parsing uint %q: %v", arg, err)) } - tv.SetUint(uint(u64)) + tv.SetUint(u64) return case gno.Uint8Type: assertNoPlusPrefix(arg) diff --git a/gno.land/pkg/sdk/vm/errors.go b/gno.land/pkg/sdk/vm/errors.go index c8d6da98970..208fb074f7e 100644 --- a/gno.land/pkg/sdk/vm/errors.go +++ b/gno.land/pkg/sdk/vm/errors.go @@ -16,6 +16,7 @@ func (abciError) AssertABCIError() {} // NOTE: these are meant to be used in conjunction with pkgs/errors. type ( InvalidPkgPathError struct{ abciError } + NoRenderDeclError struct{ abciError } PkgExistError struct{ abciError } InvalidStmtError struct{ abciError } InvalidExprError struct{ abciError } @@ -27,6 +28,7 @@ type ( ) func (e InvalidPkgPathError) Error() string { return "invalid package path" } +func (e NoRenderDeclError) Error() string { return "render function not declared" } func (e PkgExistError) Error() string { return "package already exists" } func (e InvalidStmtError) Error() string { return "invalid statement" } func (e InvalidExprError) Error() string { return "invalid expression" } diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index acde3d315c6..d706ebb834d 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -25,9 +25,8 @@ func TestAddPkgDeliverTxInsuffGas(t *testing.T) { ctx, tx, vmHandler := setupAddPkg(isValidTx) ctx = ctx.WithMode(sdk.RunTxModeDeliver) - simulate := false tx.Fee.GasWanted = 3000 - gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx := auth.SetGasMeter(ctx, tx.Fee.GasWanted) // Has to be set up after gas meter in the context; so the stores are // correctly wrapped in gas stores. gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) @@ -61,12 +60,9 @@ func TestAddPkgDeliverTx(t *testing.T) { isValidTx := true ctx, tx, vmHandler := setupAddPkg(isValidTx) - var simulate bool - ctx = ctx.WithMode(sdk.RunTxModeDeliver) - simulate = false tx.Fee.GasWanted = 500000 - gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx := auth.SetGasMeter(ctx, tx.Fee.GasWanted) gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) msgs := tx.GetMsgs() res := vmHandler.Process(gctx, msgs[0]) @@ -83,12 +79,9 @@ func TestAddPkgDeliverTxFailed(t *testing.T) { isValidTx := false ctx, tx, vmHandler := setupAddPkg(isValidTx) - var simulate bool - ctx = ctx.WithMode(sdk.RunTxModeDeliver) - simulate = false tx.Fee.GasWanted = 500000 - gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx := auth.SetGasMeter(ctx, tx.Fee.GasWanted) gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) msgs := tx.GetMsgs() res := vmHandler.Process(gctx, msgs[0]) @@ -103,12 +96,9 @@ func TestAddPkgDeliverTxFailedNoGas(t *testing.T) { isValidTx := false ctx, tx, vmHandler := setupAddPkg(isValidTx) - var simulate bool - ctx = ctx.WithMode(sdk.RunTxModeDeliver) - simulate = false tx.Fee.GasWanted = 1230 - gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + gctx := auth.SetGasMeter(ctx, tx.Fee.GasWanted) gctx = vmHandler.vm.MakeGnoTransactionStore(gctx) var res sdk.Result @@ -147,7 +137,7 @@ func setupAddPkg(success bool) (sdk.Context, sdk.Tx, vmHandler) { addr := crypto.AddressFromPreimage([]byte("test1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(ugnot.ValueString(10000000))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(ugnot.ValueString(10000000))) // success message var files []*gnovm.MemFile if success { diff --git a/gno.land/pkg/sdk/vm/genesis.go b/gno.land/pkg/sdk/vm/genesis.go new file mode 100644 index 00000000000..c508a9ff6f4 --- /dev/null +++ b/gno.land/pkg/sdk/vm/genesis.go @@ -0,0 +1,65 @@ +package vm + +import ( + "fmt" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/gnolang/gno/tm2/pkg/sdk/params" +) + +// GenesisState - all state that must be provided at genesis +type GenesisState struct { + Params Params `json:"params" yaml:"params"` + RealmParams []params.Param `json:"realm_params" yaml:"realm_params"` +} + +// NewGenesisState - Create a new genesis state +func NewGenesisState(params Params) GenesisState { + return GenesisState{ + Params: params, + } +} + +// DefaultGenesisState - Return a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(DefaultParams()) +} + +// ValidateGenesis performs basic validation of genesis data returning an +// error for any failed validation criteria. +// XXX refactor to .ValidateBasic() method. +func ValidateGenesis(gs GenesisState) error { + if amino.DeepEqual(gs, GenesisState{}) { + return fmt.Errorf("vm genesis state cannot be empty") + } + err := gs.Params.Validate() + if err != nil { + return err + } + // XXX validate RealmParams. + // 1. all keys must be realm paths. + // 2. all values must be supported types. + return nil +} + +// InitGenesis - Init store state from genesis data +func (vm *VMKeeper) InitGenesis(ctx sdk.Context, gs GenesisState) { + if err := ValidateGenesis(gs); err != nil { + panic(err) + } + if err := vm.SetParams(ctx, gs.Params); err != nil { + panic(err) + } + // NOTE realm params should not have side effects so the order + // shouldn't matter, but amino doesn't support maps (for determinism). + for _, rp := range gs.RealmParams { + vm.prmk.SetAny(ctx, "vm:"+rp.Key, rp.Value) + } +} + +// ExportGenesis returns a GenesisState for a given context and keeper +func (vm *VMKeeper) ExportGenesis(ctx sdk.Context) GenesisState { + params := vm.GetParams(ctx) + return NewGenesisState(params) +} diff --git a/gno.land/pkg/sdk/vm/handler.go b/gno.land/pkg/sdk/vm/handler.go index c484e07e887..4e9903bf44a 100644 --- a/gno.land/pkg/sdk/vm/handler.go +++ b/gno.land/pkg/sdk/vm/handler.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/gnolang/gno/gnovm/pkg/version" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" @@ -68,12 +69,11 @@ func (vh vmHandler) handleMsgRun(ctx sdk.Context, msg MsgRun) (res sdk.Result) { // query paths const ( - QueryPackage = "package" - QueryStore = "store" - QueryRender = "qrender" - QueryFuncs = "qfuncs" - QueryEval = "qeval" - QueryFile = "qfile" + QueryRender = "qrender" + QueryFuncs = "qfuncs" + QueryEval = "qeval" + QueryFile = "qfile" + QueryDoc = "qdoc" ) func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) abci.ResponseQuery { @@ -83,10 +83,6 @@ func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) abci.ResponseQ ) switch path { - case QueryPackage: - res = vh.queryPackage(ctx, req) - case QueryStore: - res = vh.queryStore(ctx, req) case QueryRender: res = vh.queryRender(ctx, req) case QueryFuncs: @@ -95,6 +91,8 @@ func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) abci.ResponseQ res = vh.queryEval(ctx, req) case QueryFile: res = vh.queryFile(ctx, req) + case QueryDoc: + res = vh.queryDoc(ctx, req) default: return sdk.ABCIResponseQueryFromError( std.ErrUnknownRequest(fmt.Sprintf( @@ -105,18 +103,6 @@ func (vh vmHandler) Query(ctx sdk.Context, req abci.RequestQuery) abci.ResponseQ return res } -// queryPackage fetch a package's files. -func (vh vmHandler) queryPackage(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - res.Data = []byte(fmt.Sprintf("TODO: parse parts get or make fileset...")) - return -} - -// queryPackage fetch items from the store. -func (vh vmHandler) queryStore(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - res.Data = []byte(fmt.Sprintf("TODO: fetch from store")) - return -} - // queryRender calls .Render() in readonly mode. func (vh vmHandler) queryRender(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { reqData := string(req.Data) @@ -129,9 +115,13 @@ func (vh vmHandler) queryRender(ctx sdk.Context, req abci.RequestQuery) (res abc expr := fmt.Sprintf("Render(%q)", path) result, err := vh.vm.QueryEvalString(ctx, pkgPath, expr) if err != nil { + if strings.Contains(err.Error(), "Render not declared") { + err = NoRenderDeclError{} + } res = sdk.ABCIResponseQueryFromError(err) return } + res.Data = []byte(result) return } @@ -197,11 +187,25 @@ func (vh vmHandler) queryFile(ctx sdk.Context, req abci.RequestQuery) (res abci. return } +// queryDoc returns the JSON of the doc for a given pkgpath, suitable for printing +func (vh vmHandler) queryDoc(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { + filepath := string(req.Data) + jsonDoc, err := vh.vm.QueryDoc(ctx, filepath) + if err != nil { + res = sdk.ABCIResponseQueryFromError(err) + return + } + res.Data = []byte(jsonDoc.JSON()) + return +} + // ---------------------------------------- // misc func abciResult(err error) sdk.Result { - return sdk.ABCIResultFromError(err) + res := sdk.ABCIResultFromError(err) + res.Info += "vm.version=" + version.Version + return res } // returns the second component of a path. diff --git a/gno.land/pkg/sdk/vm/handler_test.go b/gno.land/pkg/sdk/vm/handler_test.go index 0d238deed1f..3bac0c941c1 100644 --- a/gno.land/pkg/sdk/vm/handler_test.go +++ b/gno.land/pkg/sdk/vm/handler_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/doc" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" @@ -50,7 +51,7 @@ func Test_parseQueryEval_panic(t *testing.T) { t.Parallel() assert.PanicsWithValue(t, panicInvalidQueryEvalData, func() { - parseQueryEvalData("gno.land/r/demo/users") + parseQueryEvalData("gno.land/r/sys/users") }) } @@ -81,11 +82,15 @@ func TestVmHandlerQuery_Eval(t *testing.T) { {input: []byte(`gno.land/r/hello.myStructInst`), expectedResult: `(struct{(1000 int)} gno.land/r/hello.myStruct)`}, {input: []byte(`gno.land/r/hello.myStructInst.Foo()`), expectedResult: `("myStruct.Foo" string)`}, {input: []byte(`gno.land/r/hello.myStruct`), expectedResultMatch: `\(typeval{gno.land/r/hello.myStruct \(0x.*\)} type{}\)`}, - {input: []byte(`gno.land/r/hello.Inc`), expectedResult: `(Inc func()( int))`}, + {input: []byte(`gno.land/r/hello.Inc`), expectedResult: `(Inc func() int)`}, {input: []byte(`gno.land/r/hello.fn()("hi")`), expectedResult: `("echo:hi" string)`}, {input: []byte(`gno.land/r/hello.sl`), expectedResultMatch: `(slice[ref(.*)] []int)`}, // XXX: should return the actual value {input: []byte(`gno.land/r/hello.sl[1]`), expectedResultMatch: `(slice[ref(.*)] []int)`}, // XXX: should return the actual value {input: []byte(`gno.land/r/hello.println(1234)`), expectedResultMatch: `^$`}, // XXX: compare stdout? + { + input: []byte(`gno.land/r/hello.func() string { return "hello123" + pvString }()`), + expectedResult: `("hello123private string" string)`, + }, // panics {input: []byte(`gno.land/r/hello`), expectedPanicMatch: `expected . syntax in query input data`}, @@ -95,6 +100,7 @@ func TestVmHandlerQuery_Eval(t *testing.T) { {input: []byte(`gno.land/r/doesnotexist.Foo`), expectedErrorMatch: `^invalid package path$`}, {input: []byte(`gno.land/r/hello.Panic()`), expectedErrorMatch: `^foo$`}, {input: []byte(`gno.land/r/hello.sl[6]`), expectedErrorMatch: `^slice index out of bounds: 6 \(len=5\)$`}, + {input: []byte(`gno.land/r/hello.func(){ for {} }()`), expectedErrorMatch: `out of gas in location: CPUCycles`}, } for _, tc := range tt { @@ -108,8 +114,8 @@ func TestVmHandlerQuery_Eval(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) // Create test package. files := []*gnovm.MemFile{ @@ -120,8 +126,8 @@ import "std" import "time" var _ = time.RFC3339 -func caller() std.Address { return std.GetOrigCaller() } -var GetHeight = std.GetHeight +func caller() std.Address { return std.OriginCaller() } +var GetHeight = std.ChainHeight var sl = []int{1,2,3,4,5} func fn() func(string) string { return Echo } type myStruct struct{a int} @@ -162,7 +168,7 @@ func pvEcho(msg string) string { return "pvecho:"+msg } if tc.expectedErrorMatch == "" { assert.True(t, res.IsOK(), "should not have error") if tc.expectedResult != "" { - assert.Equal(t, string(res.Data), tc.expectedResult) + assert.Equal(t, tc.expectedResult, string(res.Data)) } if tc.expectedResultMatch != "" { assert.Regexp(t, tc.expectedResultMatch, string(res.Data)) @@ -201,8 +207,8 @@ func TestVmHandlerQuery_Funcs(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) // Create test package. files := []*gnovm.MemFile{ @@ -279,8 +285,8 @@ func TestVmHandlerQuery_File(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) // Create test package. files := []*gnovm.MemFile{ @@ -322,3 +328,118 @@ func TestVmHandlerQuery_File(t *testing.T) { }) } } + +func TestVmHandlerQuery_Doc(t *testing.T) { + expected := &doc.JSONDocumentation{ + PackagePath: "gno.land/r/hello", + PackageLine: "package hello // import \"hello\"", + PackageDoc: "hello is a package for testing\n", + Values: []*doc.JSONValueDecl{ + { + Signature: "const prefix = \"Hello\"", + Const: true, + Doc: "The prefix for the hello message\n", + Values: []*doc.JSONValue{ + { + Name: "prefix", + Doc: "", + Type: "", + }, + }, + }, + }, + Funcs: []*doc.JSONFunc{ + { + Type: "", + Name: "Hello", + Signature: "func Hello(msg string) (res string)", + Doc: "", + Params: []*doc.JSONField{ + {Name: "msg", Type: "string"}, + }, + Results: []*doc.JSONField{ + {Name: "res", Type: "string"}, + }, + }, + { + Type: "myStruct", + Name: "Foo", + Signature: "func (ms myStruct) Foo() string", + Doc: "", + Params: []*doc.JSONField{}, + Results: []*doc.JSONField{ + {Name: "", Type: "string"}, + }, + }, + }, + Types: []*doc.JSONType{ + { + Name: "myStruct", + Signature: "type myStruct struct{ a int }", + Doc: "myStruct is a struct for testing\n", + }, + }, + } + + tt := []struct { + input []byte + expectedResult string + expectedErrorMatch string + }{ + // valid queries + {input: []byte(`gno.land/r/hello`), expectedResult: expected.JSON()}, + {input: []byte(`gno.land/r/doesnotexist`), expectedErrorMatch: `invalid package path`}, + } + + for _, tc := range tt { + name := string(tc.input) + t.Run(name, func(t *testing.T) { + env := setupTestEnv() + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) + vmHandler := env.vmh + + // Give "addr1" some gnots. + addr := crypto.AddressFromPreimage([]byte("addr1")) + acc := env.acck.NewAccountWithAddress(ctx, addr) + env.acck.SetAccount(ctx, acc) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot")) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot"))) + + // Create test package. + files := []*gnovm.MemFile{ + {Name: "hello.gno", Body: ` +// hello is a package for testing +package hello + +// myStruct is a struct for testing +type myStruct struct{a int} +func (ms myStruct) Foo() string { return "myStruct.Foo" } +// The prefix for the hello message +const prefix = "Hello" +func Hello(msg string) (res string) { res = prefix+" "+msg; return } +`}, + } + pkgPath := "gno.land/r/hello" + msg1 := NewMsgAddPackage(addr, pkgPath, files) + err := env.vmk.AddPackage(ctx, msg1) + assert.NoError(t, err) + + req := abci.RequestQuery{ + Path: "vm/qdoc", + Data: tc.input, + } + + res := vmHandler.Query(env.ctx, req) + if tc.expectedErrorMatch == "" { + assert.True(t, res.IsOK(), "should not have error") + if tc.expectedResult != "" { + assert.Equal(t, tc.expectedResult, string(res.Data)) + } + } else { + assert.False(t, res.IsOK(), "should have an error") + errmsg := res.Error.Error() + assert.Regexp(t, tc.expectedErrorMatch, errmsg) + } + }) + } +} diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index bf16cd44243..827a4fe6d38 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -16,6 +16,7 @@ import ( "time" "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/doc" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -39,6 +40,7 @@ import ( const ( maxAllocTx = 500_000_000 maxAllocQuery = 1_500_000_000 // higher limit for queries + maxGasQuery = 3_000_000_000 // same as max block gas ) // vm.VMKeeperI defines a module interface that supports Gno @@ -52,6 +54,7 @@ type VMKeeperI interface { LoadStdlibCached(ctx sdk.Context, stdlibDir string) MakeGnoTransactionStore(ctx sdk.Context) sdk.Context CommitGnoTransactionStore(ctx sdk.Context) + InitGenesis(ctx sdk.Context, data GenesisState) } var _ VMKeeperI = &VMKeeper{} @@ -72,6 +75,8 @@ type VMKeeper struct { } // NewVMKeeper returns a new VMKeeper. +// NOTE: prmk must be the root ParamsKeeper such that +// ExecContext.Params may set any module's parameter. func NewVMKeeper( baseKey store.StoreKey, iavlKey store.StoreKey, @@ -234,8 +239,8 @@ var reNamespace = regexp.MustCompile(`^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/(?:r|p)/([\. // checkNamespacePermission check if the user as given has correct permssion to on the given pkg path func (vm *VMKeeper) checkNamespacePermission(ctx sdk.Context, creator crypto.Address, pkgPath string) error { - sysUsersPkg := vm.getSysUsersPkgParam(ctx) - if sysUsersPkg == "" { + sysNamesPkg := vm.getSysNamesPkgParam(ctx) + if sysNamesPkg == "" { return nil } chainDomain := vm.getChainDomainParam(ctx) @@ -254,10 +259,10 @@ func (vm *VMKeeper) checkNamespacePermission(ctx sdk.Context, creator crypto.Add default: panic("invalid pattern while matching pkgpath") } - username := match[1] + namespace := match[1] // if `sysUsersPkg` does not exist -> skip validation. - usersPkg := store.GetPackage(sysUsersPkg, false) + usersPkg := store.GetPackage(sysNamesPkg, false) if usersPkg == nil { return nil } @@ -265,16 +270,16 @@ func (vm *VMKeeper) checkNamespacePermission(ctx sdk.Context, creator crypto.Add // Parse and run the files, construct *PV. pkgAddr := gno.DerivePkgAddr(pkgPath) msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: creator.Bech32(), - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: creator.Bech32(), + OriginSendSpent: new(std.Coins), + OriginPkgAddr: pkgAddr.Bech32(), // XXX: should we remove the banker ? Banker: NewSDKBanker(vm, ctx), - Params: NewSDKParams(vm, ctx), + Params: NewSDKParams(vm.prmk, ctx), EventLogger: ctx.EventLogger(), } @@ -289,15 +294,15 @@ func (vm *VMKeeper) checkNamespacePermission(ctx sdk.Context, creator crypto.Add }) defer m.Release() - // call $sysUsersPkg.IsAuthorizedAddressForName("") - // We only need to check by name here, as address have already been check + // call sysNamesPkg.IsAuthorizedAddressForName("") + // We only need to check by name here, as addresses have already been checked mpv := gno.NewPackageNode("main", "main", nil).NewPackage() m.SetActivePackage(mpv) - m.RunDeclaration(gno.ImportD("users", sysUsersPkg)) + m.RunDeclaration(gno.ImportD("names", sysNamesPkg)) x := gno.Call( - gno.Sel(gno.Nx("users"), "IsAuthorizedAddressForName"), + gno.Sel(gno.Nx("names"), "IsAuthorizedAddressForNamespace"), gno.Str(creator.String()), - gno.Str(username), + gno.Str(namespace), ) ret := m.Eval(x) @@ -311,7 +316,11 @@ func (vm *VMKeeper) checkNamespacePermission(ctx sdk.Context, creator crypto.Add } if isAuthorized := useraddress.GetBool(); !isAuthorized { - return ErrUnauthorizedUser(username) + return ErrUnauthorizedUser( + fmt.Sprintf("%s is not authorized to deploy packages to namespace `%s`", + creator.String(), + namespace, + )) } return nil @@ -370,17 +379,17 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { // Parse and run the files, construct *PV. msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: creator.Bech32(), - OrigSend: deposit, - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: creator.Bech32(), + OriginSend: deposit, + OriginSendSpent: new(std.Coins), + OriginPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm.prmk, ctx), + EventLogger: ctx.EventLogger(), } // Parse and run the files, construct *PV. m2 := gno.NewMachineWithOptions( @@ -461,17 +470,17 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { // could it be safely partially memoized? chainDomain := vm.getChainDomainParam(ctx) msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: caller.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: caller.Bech32(), + OriginSend: send, + OriginSendSpent: new(std.Coins), + OriginPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm.prmk, ctx), + EventLogger: ctx.EventLogger(), } // Construct machine and evaluate. m := gno.NewMachineWithOptions( @@ -512,21 +521,38 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { func doRecover(m *gno.Machine, e *error) { r := recover() + + // On normal transaction execution, out of gas panics are handled in the + // BaseApp, so repanic here. + const repanicOutOfGas = true + doRecoverInternal(m, e, r, repanicOutOfGas) +} + +func doRecoverQuery(m *gno.Machine, e *error) { + r := recover() + const repanicOutOfGas = false + doRecoverInternal(m, e, r, repanicOutOfGas) +} + +func doRecoverInternal(m *gno.Machine, e *error, r any, repanicOutOfGas bool) { if r == nil { return } if err, ok := r.(error); ok { var oog types.OutOfGasError if goerrors.As(err, &oog) { - // Re-panic and don't wrap. - panic(oog) + if repanicOutOfGas { + panic(oog) + } + *e = oog + return } var up gno.UnhandledPanicError if goerrors.As(err, &up) { // Common unhandled panic error, skip machine state. *e = errors.Wrapf( errors.New(up.Descriptor), - "VM panic: %s\nStacktrace: %s\n", + "VM panic: %s\nStacktrace:\n%s\n", up.Descriptor, m.ExceptionsStacktrace(), ) return @@ -534,7 +560,7 @@ func doRecover(m *gno.Machine, e *error) { } *e = errors.Wrapf( fmt.Errorf("%v", r), - "VM panic: %v\nMachine State:%s\nStacktrace: %s\n", + "VM panic: %v\nMachine State:%s\nStacktrace:\n%s\n", r, m.String(), m.Stacktrace().String(), ) } @@ -576,17 +602,17 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { // Parse and run the files, construct *PV. msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - OrigCaller: caller.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + ChainID: ctx.ChainID(), + ChainDomain: chainDomain, + Height: ctx.BlockHeight(), + Timestamp: ctx.BlockTime().Unix(), + OriginCaller: caller.Bech32(), + OriginSend: send, + OriginSendSpent: new(std.Coins), + OriginPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), + Params: NewSDKParams(vm.prmk, ctx), + EventLogger: ctx.EventLogger(), } buf := new(bytes.Buffer) @@ -708,51 +734,11 @@ func (vm *VMKeeper) QueryFuncs(ctx sdk.Context, pkgPath string) (fsigs FunctionS } // QueryEval evaluates a gno expression (readonly, for ABCI queries). -// TODO: modify query protocol to allow MsgEval. -// TODO: then, rename to "Eval". func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res string, err error) { - alloc := gno.NewAllocator(maxAllocQuery) - gnostore := vm.newGnoTransactionStore(ctx) // throwaway (never committed) - pkgAddr := gno.DerivePkgAddr(pkgPath) - // Get Package. - pv := gnostore.GetPackage(pkgPath, false) - if pv == nil { - err = ErrInvalidPkgPath(fmt.Sprintf( - "package not found: %s", pkgPath)) - return "", err - } - // Parse expression. - xx, err := gno.ParseExpr(expr) + rtvs, err := vm.queryEvalInternal(ctx, pkgPath, expr) if err != nil { return "", err } - // Construct new machine. - chainDomain := vm.getChainDomainParam(ctx) - msgCtx := stdlibs.ExecContext{ - ChainID: ctx.ChainID(), - ChainDomain: chainDomain, - Height: ctx.BlockHeight(), - Timestamp: ctx.BlockTime().Unix(), - // OrigCaller: caller, - // OrigSend: send, - // OrigSendSpent: nil, - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), - } - m := gno.NewMachineWithOptions( - gno.MachineOptions{ - PkgPath: pkgPath, - Output: vm.Output, - Store: gnostore, - Context: msgCtx, - Alloc: alloc, - GasMeter: ctx.GasMeter(), - }) - defer m.Release() - defer doRecover(m, &err) - rtvs := m.Eval(xx) res = "" for i, rtv := range rtvs { res += rtv.String() @@ -765,9 +751,22 @@ func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res // QueryEvalString evaluates a gno expression (readonly, for ABCI queries). // The result is expected to be a single string (not a tuple). -// TODO: modify query protocol to allow MsgEval. -// TODO: then, rename to "EvalString". func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string) (res string, err error) { + rtvs, err := vm.queryEvalInternal(ctx, pkgPath, expr) + if err != nil { + return "", err + } + if len(rtvs) != 1 { + return "", errors.New("expected 1 string result, got %d", len(rtvs)) + } else if rtvs[0].T.Kind() != gno.StringKind { + return "", errors.New("expected 1 string result, got %v", rtvs[0].T.Kind()) + } + res = rtvs[0].GetString() + return res, nil +} + +func (vm *VMKeeper) queryEvalInternal(ctx sdk.Context, pkgPath string, expr string) (rtvs []gno.TypedValue, err error) { + ctx = ctx.WithGasMeter(store.NewGasMeter(maxGasQuery)) alloc := gno.NewAllocator(maxAllocQuery) gnostore := vm.newGnoTransactionStore(ctx) // throwaway (never committed) pkgAddr := gno.DerivePkgAddr(pkgPath) @@ -776,12 +775,12 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string if pv == nil { err = ErrInvalidPkgPath(fmt.Sprintf( "package not found: %s", pkgPath)) - return "", err + return nil, err } // Parse expression. xx, err := gno.ParseExpr(expr) if err != nil { - return "", err + return nil, err } // Construct new machine. chainDomain := vm.getChainDomainParam(ctx) @@ -791,12 +790,12 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string Height: ctx.BlockHeight(), Timestamp: ctx.BlockTime().Unix(), // OrigCaller: caller, - // OrigSend: jsend, + // OrigSend: send, // OrigSendSpent: nil, - OrigPkgAddr: pkgAddr.Bech32(), - Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. - Params: NewSDKParams(vm, ctx), - EventLogger: ctx.EventLogger(), + OriginPkgAddr: pkgAddr.Bech32(), + Banker: NewSDKBanker(vm, ctx), // safe as long as ctx is a fork to be discarded. + Params: NewSDKParams(vm.prmk, ctx), + EventLogger: ctx.EventLogger(), } m := gno.NewMachineWithOptions( gno.MachineOptions{ @@ -808,15 +807,8 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string GasMeter: ctx.GasMeter(), }) defer m.Release() - defer doRecover(m, &err) - rtvs := m.Eval(xx) - if len(rtvs) != 1 { - return "", errors.New("expected 1 string result, got %d", len(rtvs)) - } else if rtvs[0].T.Kind() != gno.StringKind { - return "", errors.New("expected 1 string result, got %v", rtvs[0].T.Kind()) - } - res = rtvs[0].GetString() - return res, nil + defer doRecoverQuery(m, &err) + return m.Eval(xx), err } func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err error) { @@ -843,6 +835,22 @@ func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err } } +func (vm *VMKeeper) QueryDoc(ctx sdk.Context, pkgPath string) (*doc.JSONDocumentation, error) { + store := vm.newGnoTransactionStore(ctx) // throwaway (never committed) + + memPkg := store.GetMemPackage(pkgPath) + if memPkg == nil { + err := ErrInvalidPkgPath(fmt.Sprintf( + "package not found: %s", pkgPath)) + return nil, err + } + d, err := doc.NewDocumentableFromMemPkg(memPkg, true, "", "") + if err != nil { + return nil, err + } + return d.WriteJSONDocumentation() +} + // logTelemetry logs the VM processing telemetry func logTelemetry( gasUsed int64, diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index f8144988c44..4df57d9cdbd 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -32,8 +32,8 @@ func TestVMKeeperAddPackage(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ @@ -76,8 +76,8 @@ func TestVMKeeperAddPackage_InvalidDomain(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ @@ -106,7 +106,7 @@ func Echo() string {return "hello world"}`, } // Sending total send amount succeeds. -func TestVMKeeperOrigSend1(t *testing.T) { +func TestVMKeeperOriginSend1(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -114,8 +114,8 @@ func TestVMKeeperOrigSend1(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ @@ -128,10 +128,10 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() - send := std.GetOrigSend() - banker := std.GetBanker(std.BankerTypeOrigSend) + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() + send := std.OriginSend() + banker := std.NewBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg }`}, @@ -151,7 +151,7 @@ func Echo(msg string) string { } // Sending too much fails -func TestVMKeeperOrigSend2(t *testing.T) { +func TestVMKeeperOriginSend2(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -159,8 +159,8 @@ func TestVMKeeperOrigSend2(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ @@ -172,14 +172,14 @@ import "std" var admin std.Address func init() { - admin = std.GetOrigCaller() + admin = std.OriginCaller() } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() - send := std.GetOrigSend() - banker := std.GetBanker(std.BankerTypeOrigSend) + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() + send := std.OriginSend() + banker := std.NewBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg } @@ -200,12 +200,11 @@ func GetAdmin() string { res, err := env.vmk.Call(ctx, msg2) assert.Error(t, err) assert.Equal(t, "", res) - fmt.Println(err.Error()) assert.True(t, strings.Contains(err.Error(), "insufficient coins error")) } // Sending more than tx send fails. -func TestVMKeeperOrigSend3(t *testing.T) { +func TestVMKeeperOriginSend3(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -213,8 +212,8 @@ func TestVMKeeperOrigSend3(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ @@ -227,10 +226,10 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() send := std.Coins{{"ugnot", 10000000}} - banker := std.GetBanker(std.BankerTypeOrigSend) + banker := std.NewBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg }`}, @@ -257,8 +256,8 @@ func TestVMKeeperRealmSend1(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ @@ -271,10 +270,10 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() send := std.Coins{{"ugnot", 10000000}} - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg }`}, @@ -301,8 +300,8 @@ func TestVMKeeperRealmSend2(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ @@ -315,10 +314,10 @@ func init() { } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() send := std.Coins{{"ugnot", 10000000}} - banker := std.GetBanker(std.BankerTypeRealmSend) + banker := std.NewBanker(std.BankerTypeRealmSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg }`}, @@ -345,14 +344,14 @@ func TestVMKeeperParams(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) // env.prmk. - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ {Name: "init.gno", Body: ` -package test +package params import "std" @@ -367,7 +366,7 @@ func Do() string { return "XXX" // return std.GetConfig("gno.land/r/test.foo"), if we want to expose std.GetConfig, maybe as a std.TestGetConfig }`}, } - pkgPath := "gno.land/r/test" + pkgPath := "gno.land/r/myuser/myrealm" msg1 := NewMsgAddPackage(addr, pkgPath, files) err := env.vmk.AddPackage(ctx, msg1) assert.NoError(t, err) @@ -384,14 +383,14 @@ func Do() string { var foo string var bar int64 - env.vmk.prmk.GetString(ctx, "gno.land/r/test.foo.string", &foo) - env.vmk.prmk.GetInt64(ctx, "gno.land/r/test.bar.int64", &bar) + env.vmk.prmk.GetString(ctx, "vm:gno.land/r/myuser/myrealm:foo.string", &foo) + env.vmk.prmk.GetInt64(ctx, "vm:gno.land/r/myuser/myrealm:bar.int64", &bar) assert.Equal(t, "foo2", foo) assert.Equal(t, int64(1337), bar) } -// Assign admin as OrigCaller on deploying the package. -func TestVMKeeperOrigCallerInit(t *testing.T) { +// Assign admin as OriginCaller on deploying the package. +func TestVMKeeperOriginCallerInit(t *testing.T) { env := setupTestEnv() ctx := env.vmk.MakeGnoTransactionStore(env.ctx) @@ -399,8 +398,8 @@ func TestVMKeeperOrigCallerInit(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ @@ -412,14 +411,14 @@ import "std" var admin std.Address func init() { - admin = std.GetOrigCaller() + admin = std.OriginCaller() } func Echo(msg string) string { - addr := std.GetOrigCaller() - pkgAddr := std.GetOrigPkgAddr() - send := std.GetOrigSend() - banker := std.GetBanker(std.BankerTypeOrigSend) + addr := std.OriginCaller() + pkgAddr := std.OriginPkgAddress() + send := std.OriginSend() + banker := std.NewBanker(std.BankerTypeOriginSend) banker.SendCoins(pkgAddr, addr, send) // send back return "echo:"+msg } @@ -500,7 +499,7 @@ package main import "std" func main() { - addr := std.GetOrigCaller() + addr := std.OriginCaller() println("hello world!", addr) } `}, @@ -522,8 +521,8 @@ func TestNumberOfArgsError(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ @@ -561,8 +560,8 @@ func TestVMKeeperReinitialize(t *testing.T) { addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr) env.acck.SetAccount(ctx, acc) - env.bank.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) - assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) + env.bankk.SetCoins(ctx, addr, std.MustParseCoins(coinsString)) + assert.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.MustParseCoins(coinsString))) // Create test package. files := []*gnovm.MemFile{ diff --git a/gno.land/pkg/sdk/vm/msgs.go b/gno.land/pkg/sdk/vm/msgs.go index 38f35ab7110..51d573837fc 100644 --- a/gno.land/pkg/sdk/vm/msgs.go +++ b/gno.land/pkg/sdk/vm/msgs.go @@ -120,6 +120,9 @@ func (msg MsgCall) ValidateBasic() error { if !gno.IsRealmPath(msg.PkgPath) { return ErrInvalidPkgPath("pkgpath must be of a realm") } + if _, isInt := gno.IsInternalPath(msg.PkgPath); isInt { + return ErrInvalidPkgPath("pkgpath must not be of an internal package") + } if msg.Func == "" { // XXX return ErrInvalidExpr("missing function to call") } diff --git a/gno.land/pkg/sdk/vm/msg_test.go b/gno.land/pkg/sdk/vm/msgs_test.go similarity index 95% rename from gno.land/pkg/sdk/vm/msg_test.go rename to gno.land/pkg/sdk/vm/msgs_test.go index 684dc21e9f2..56b3931ef8d 100644 --- a/gno.land/pkg/sdk/vm/msg_test.go +++ b/gno.land/pkg/sdk/vm/msgs_test.go @@ -164,6 +164,20 @@ func TestMsgCall_ValidateBasic(t *testing.T) { }, expectErr: InvalidPkgPathError{}, }, + { + name: "pkgPath should not be an internal path", + msg: MsgCall{ + Caller: caller, + PkgPath: "gno.land/r/demo/avl/internal/sort", + Func: funcName, + Args: args, + Send: std.Coins{std.Coin{ + Denom: "ugnot", + Amount: 1000, + }}, + }, + expectErr: InvalidPkgPathError{}, + }, { name: "missing function name to call", msg: MsgCall{ diff --git a/gno.land/pkg/sdk/vm/package.go b/gno.land/pkg/sdk/vm/package.go index 0359061ccea..95e97648dac 100644 --- a/gno.land/pkg/sdk/vm/package.go +++ b/gno.land/pkg/sdk/vm/package.go @@ -20,6 +20,7 @@ var Package = amino.RegisterPackage(amino.NewPackage( // errors InvalidPkgPathError{}, "InvalidPkgPathError", + NoRenderDeclError{}, "NoRenderDeclError", PkgExistError{}, "PkgExistError", InvalidStmtError{}, "InvalidStmtError", InvalidExprError{}, "InvalidExprError", diff --git a/gno.land/pkg/sdk/vm/params.go b/gno.land/pkg/sdk/vm/params.go index 248fb8a81fb..c4c2ad530a2 100644 --- a/gno.land/pkg/sdk/vm/params.go +++ b/gno.land/pkg/sdk/vm/params.go @@ -1,20 +1,96 @@ package vm -import "github.com/gnolang/gno/tm2/pkg/sdk" +import ( + "fmt" + "regexp" + "strings" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/sdk" +) + +const ( + sysNamesPkgDefault = "gno.land/r/sys/names" + chainDomainDefault = "gno.land" +) + +var ASCIIDomain = regexp.MustCompile(`^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,}$`) + +// Params defines the parameters for the bank module. +type Params struct { + SysNamesPkgPath string `json:"sysnames_pkgpath" yaml:"sysnames_pkgpath"` + ChainDomain string `json:"chain_domain" yaml:"chain_domain"` +} + +// NewParams creates a new Params object +func NewParams(namesPkgPath, chainDomain string) Params { + return Params{ + SysNamesPkgPath: namesPkgPath, + ChainDomain: chainDomain, + } +} + +// DefaultParams returns a default set of parameters. +func DefaultParams() Params { + return NewParams(sysNamesPkgDefault, chainDomainDefault) +} + +// String implements the stringer interface. +func (p Params) String() string { + var sb strings.Builder + sb.WriteString("Params: \n") + sb.WriteString(fmt.Sprintf("SysUsersPkgPath: %q\n", p.SysNamesPkgPath)) + sb.WriteString(fmt.Sprintf("ChainDomain: %q\n", p.ChainDomain)) + return sb.String() +} + +func (p Params) Validate() error { + if p.SysNamesPkgPath != "" && !gno.ReRealmPath.MatchString(p.SysNamesPkgPath) { + return fmt.Errorf("invalid package/realm path %q, failed to match %q", p.SysNamesPkgPath, gno.ReRealmPath) + } + if p.ChainDomain != "" && !ASCIIDomain.MatchString(p.ChainDomain) { + return fmt.Errorf("invalid chain domain %q, failed to match %q", p.ChainDomain, ASCIIDomain) + } + return nil +} + +// Equals returns a boolean determining if two Params types are identical. +func (p Params) Equals(p2 Params) bool { + return amino.DeepEqual(p, p2) +} + +func (vm *VMKeeper) SetParams(ctx sdk.Context, params Params) error { + if err := params.Validate(); err != nil { + return err + } + vm.prmk.SetStruct(ctx, "vm:p", params) // prmk is root. + return nil +} + +func (vm *VMKeeper) GetParams(ctx sdk.Context) Params { + params := Params{} + vm.prmk.GetStruct(ctx, "vm:p", ¶ms) // prmk is root. + return params +} const ( - sysUsersPkgParamPath = "gno.land/r/sys/params.sys.users_pkgpath.string" - chainDomainParamPath = "gno.land/r/sys/params.chain_domain.string" + sysUsersPkgParamPath = "vm:p:sysnames_pkgpath" + chainDomainParamPath = "vm:p:chain_domain" ) func (vm *VMKeeper) getChainDomainParam(ctx sdk.Context) string { - chainDomain := "gno.land" // default + chainDomain := chainDomainDefault // default vm.prmk.GetString(ctx, chainDomainParamPath, &chainDomain) return chainDomain } -func (vm *VMKeeper) getSysUsersPkgParam(ctx sdk.Context) string { - var sysUsersPkg string - vm.prmk.GetString(ctx, sysUsersPkgParamPath, &sysUsersPkg) - return sysUsersPkg +func (vm *VMKeeper) getSysNamesPkgParam(ctx sdk.Context) string { + sysNamesPkg := sysNamesPkgDefault + vm.prmk.GetString(ctx, sysUsersPkgParamPath, &sysNamesPkg) + return sysNamesPkg +} + +func (vm *VMKeeper) WillSetParam(ctx sdk.Context, key string, value interface{}) { + // XXX validate input? } diff --git a/gno.land/pkg/sdk/vm/params_test.go b/gno.land/pkg/sdk/vm/params_test.go new file mode 100644 index 00000000000..759051d97b9 --- /dev/null +++ b/gno.land/pkg/sdk/vm/params_test.go @@ -0,0 +1,130 @@ +package vm + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestParamsString verifies the output of the String method. +func TestParamsString(t *testing.T) { + p := Params{ + SysNamesPkgPath: "gno.land/r/sys/names", // XXX what is this really for now + ChainDomain: "example.com", + } + result := p.String() + + // Construct the expected string. + expected := "Params: \n" + + fmt.Sprintf("SysUsersPkgPath: %q\n", p.SysNamesPkgPath) + + fmt.Sprintf("ChainDomain: %q\n", p.ChainDomain) + + // Assert: check if the result matches the expected string. + if result != expected { + t.Errorf("Params.String() = %q; want %q", result, expected) + } +} + +func TestWillSetParam(t *testing.T) { + env := setupTestEnv() + ctx := env.vmk.MakeGnoTransactionStore(env.ctx) + vmk := env.vmk + prmk := env.prmk + dps := DefaultParams() + + tests := []struct { + name string + key string + value string + getExpectedValue func(prms Params) string + shouldPanic bool + isUpdated bool + isEqual bool + }{ + { + name: "update sysnames_pkgpath", + key: "sysnames_pkgpath", + value: "gno.land/r/foo", + getExpectedValue: func(prms Params) string { + return prms.SysNamesPkgPath + }, + shouldPanic: false, + isUpdated: true, + isEqual: true, + }, + { + name: "update chain_domain", + key: "chain_domain", + value: "example.com", + getExpectedValue: func(prms Params) string { + return prms.ChainDomain + }, + shouldPanic: false, + isUpdated: true, + isEqual: true, + }, + /* unknown parameter keys are OK + { + name: "unknown parameter key panics", + key: "unknown_key", + value: "some value", + getExpectedValue: nil, + shouldPanic: true, + isUpdated: false, + isEqual: false, // Not applicable, but included for consistency + }, + */ + { + name: "non-empty realm does not update params", + key: "gno.land/r/sys/params.sysnames_pkgpath", + value: "gno.land/r/foo", + getExpectedValue: func(prms Params) string { + return prms.SysNamesPkgPath // Expect unchanged value + }, + shouldPanic: false, + isUpdated: false, + isEqual: false, + }, + /* XXX add verification in willSetParam(). + { + name: "invalid pkgpath panics", + key: "sysusers_pkgpath", + value: "path/to/pkg", + shouldPanic: true, + isUpdated: false, + isEqual: false, // Not applicable + }, + { + name: "invalid domain panics", + key: "chain_domain", + value: "example/com", + shouldPanic: true, + isUpdated: false, + isEqual: false, // Not applicable + }, + */ + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + assert.Panics(t, func() { + prmk.SetString(ctx, "vm:p:"+tt.key, tt.value) + }, "expected panic for test: %s", tt.name) + } else { + prmk.SetString(ctx, "vm:p:"+tt.key, tt.value) + if tt.getExpectedValue != nil { + prms := vmk.GetParams(ctx) + if tt.isUpdated { + assert.False(t, prms.Equals(dps), "expected values to be different") + } + if tt.isEqual { + actual := tt.getExpectedValue(prms) + assert.Equal(t, tt.value, actual, "expected values to match") + } + } + } + }) + } +} diff --git a/gnovm/Makefile b/gnovm/Makefile index d724ffbb6a2..915bf294814 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -27,11 +27,14 @@ GOTEST_FLAGS ?= -v -p 1 -timeout=30m GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../) # We can't use '-trimpath' yet as amino use absolute path from call stack # to find some directory: see #1236 -GOBUILD_FLAGS ?= -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)" +GOBUILD_FLAGS ?= -ldflags "-X github.com/gnolang/gno/gnovm/pkg/version.Version=$(VERSION) -X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)" # file where to place cover profile; used for coverage commands which are # more complex than adding -coverprofile, like test.cmd.coverage. GOTEST_COVER_PROFILE ?= cmd-profile.out +# user for gno version [branch].[N]+[hash] +VERSION ?= $(shell git describe --tags --exact-match 2>/dev/null || echo "$(shell git rev-parse --abbrev-ref HEAD).$(shell git rev-list --count HEAD)+$(shell git rev-parse --short HEAD)") + ######################################## # Dev tools .PHONY: build @@ -54,8 +57,8 @@ lint: .PHONY: fmt fmt: - go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/... ./tests/stdlibs/... $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) . + go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/... ./tests/stdlibs/... .PHONY: imports imports: @@ -84,7 +87,7 @@ run.bench.storage: build.bench.storage ######################################## # Test suite .PHONY: test -test: _test.cmd _test.pkg _test.stdlibs +test: _test.filetest _test.cmd _test.pkg _test.stdlibs .PHONY: _test.cmd _test.cmd: @@ -114,27 +117,23 @@ _test.pkg: .PHONY: _test.stdlibs _test.stdlibs: + go test -v ./stdlibs/... go run ./cmd/gno test -v ./stdlibs/... +.PHONY: _test.filetest _test.filetest:; go test pkg/gnolang/files_test.go -test.short -run 'TestFiles$$/' $(GOTEST_FLAGS) ######################################## # 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: - $(stringer_cmd) -type=Kind ./pkg/gnolang - $(stringer_cmd) -type=Op ./pkg/gnolang - $(stringer_cmd) -type=TransCtrl ./pkg/gnolang - $(stringer_cmd) -type=TransField ./pkg/gnolang - $(stringer_cmd) -type=VPType ./pkg/gnolang - $(stringer_cmd) -type=Word ./pkg/gnolang +generate: _dev.generate _dev.docs fmt imports + +.PHONY: _dev.docs +_dev.docs: + mkdir -p .tmp + (go run ./cmd/gno -h 2>&1 || true) | grep -v "exit status 1" > .tmp/gno-help.txt + $(rundep) github.com/campoy/embedmd -w `find . -name "*.md"` .PHONY: _dev.generate _dev.generate: diff --git a/gnovm/cmd/gno/README.md b/gnovm/cmd/gno/README.md index f900b164783..81d3de2cf62 100644 --- a/gnovm/cmd/gno/README.md +++ b/gnovm/cmd/gno/README.md @@ -6,13 +6,26 @@ `gno [arguments]` -## Commands +## Usage -* `gno run` - run a Gno file -* `gno transpile` - transpile .gno to .go -* `gno test` - test a gno package -* `gno mod` - manages dependencies -* `gno repl` start a GnoVM REPL +[embedmd]:#(../../.tmp/gno-help.txt) +```txt +USAGE + gno [arguments] + +SUBCOMMANDS + bug start a bug report + clean remove generated and cached data + doc show documentation for package or symbol + env print gno environment information + fmt gnofmt (reformat) package sources + mod module maintenance + run run gno packages + test test packages + tool run specified gno tool + version display installed gno version + +``` ## Install diff --git a/gnovm/cmd/gno/bug.go b/gnovm/cmd/gno/bug.go index a1273d9ad59..fd5f907bc40 100644 --- a/gnovm/cmd/gno/bug.go +++ b/gnovm/cmd/gno/bug.go @@ -11,6 +11,7 @@ import ( "text/template" "time" + "github.com/gnolang/gno/gnovm/pkg/version" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -23,9 +24,10 @@ Describe your issue in as much detail as possible here ### Your environment -* Go version: {{.GoVersion}} -* OS and CPU architecture: {{.Os}}/{{.Arch}} -* Gno commit hash causing the issue: {{.Commit}} +* Gno version: {{ .GnoVersion }} +* Go version: {{ .GoVersion }} +* OS and CPU architecture: {{ .Os }}/{{ .Arch }} +* Gno commit hash causing the issue: {{ .Commit }} ### Steps to reproduce @@ -61,12 +63,13 @@ func newBugCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "bug", ShortUsage: "bug", - ShortHelp: "Start a bug report", - LongHelp: `opens https://github.com/gnolang/gno/issues in a browser. + ShortHelp: "start a bug report", + LongHelp: `opens https://github.com/gnolang/gno/issues in a browser. The new issue body is prefilled for you with the following information: -- Go version (example: go1.22.4) +- Gno version (the output of "gno version") +- Go version (example: go1.23.4) - OS and CPU architecture (example: linux/amd64) - Gno commit hash causing the issue (example: f24690e7ebf325bffcfaf9e328c3df8e6b21e50c) @@ -96,10 +99,11 @@ func execBug(cfg *bugCfg, args []string, io commands.IO) error { } bugReportEnv := struct { - Os, Arch, GoVersion, Commit string + Os, Arch, GnoVersion, GoVersion, Commit string }{ runtime.GOOS, runtime.GOARCH, + version.Version, runtime.Version(), getCommitHash(), } diff --git a/gnovm/cmd/gno/bug_test.go b/gnovm/cmd/gno/bug_test.go index 516bfd4081b..4b0073ce014 100644 --- a/gnovm/cmd/gno/bug_test.go +++ b/gnovm/cmd/gno/bug_test.go @@ -5,17 +5,21 @@ import "testing" func TestBugApp(t *testing.T) { tc := []testMainCase{ { - args: []string{"bug -h"}, - errShouldBe: "flag: help requested", + args: []string{"bug", "-h"}, + errShouldContain: "flag: help requested", }, { - args: []string{"bug unknown"}, + args: []string{"bug", "unknown"}, errShouldBe: "flag: help requested", }, { args: []string{"bug", "-skip-browser"}, stdoutShouldContain: "Go version: go1.", }, + { + args: []string{"bug", "-skip-browser"}, + stdoutShouldContain: "Gno version: develop", + }, } testMainCaseRun(t, tc) } diff --git a/gnovm/cmd/gno/clean.go b/gnovm/cmd/gno/clean.go index 0ca2e940d58..7c318c3957e 100644 --- a/gnovm/cmd/gno/clean.go +++ b/gnovm/cmd/gno/clean.go @@ -26,7 +26,7 @@ func newCleanCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "clean", ShortUsage: "clean [flags]", - ShortHelp: "removes generated files and cached data", + ShortHelp: "remove generated and cached data", }, cfg, func(ctx context.Context, args []string) error { diff --git a/gnovm/cmd/gno/doc.go b/gnovm/cmd/gno/doc.go index 794dd1ba7bb..c34ed984d9c 100644 --- a/gnovm/cmd/gno/doc.go +++ b/gnovm/cmd/gno/doc.go @@ -29,7 +29,8 @@ func newDocCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "doc", ShortUsage: "doc [flags] ", - ShortHelp: "get documentation for the specified package or symbol (type, function, method, or variable/constant)", + ShortHelp: "show documentation for package or symbol", + LongHelp: "get documentation for the specified package or symbol (type, function, method, or variable/constant)", }, c, func(_ context.Context, args []string) error { diff --git a/gnovm/cmd/gno/download_deps_test.go b/gnovm/cmd/gno/download_deps_test.go index 0828e9b2245..15c344cbd28 100644 --- a/gnovm/cmd/gno/download_deps_test.go +++ b/gnovm/cmd/gno/download_deps_test.go @@ -46,9 +46,10 @@ func TestDownloadDeps(t *testing.T) { }, }, }, - requirements: []string{"avl"}, + requirements: []string{"avl", "ufmt"}, ioErrContains: []string{ "gno: downloading gno.land/p/demo/avl", + "gno: downloading gno.land/p/demo/ufmt", }, }, { desc: "fetch_gno.land/p/demo/blog6", @@ -80,9 +81,10 @@ func TestDownloadDeps(t *testing.T) { New: module.Version{Path: "gno.land/p/demo/avl"}, }}, }, - requirements: []string{"avl"}, + requirements: []string{"avl", "ufmt"}, ioErrContains: []string{ "gno: downloading gno.land/p/demo/avl", + "gno: downloading gno.land/p/demo/ufmt", }, }, { desc: "fetch_replace_local", diff --git a/gnovm/cmd/gno/env.go b/gnovm/cmd/gno/env.go index 9c601a270c7..20d32c85ec0 100644 --- a/gnovm/cmd/gno/env.go +++ b/gnovm/cmd/gno/env.go @@ -18,7 +18,7 @@ func newEnvCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "env", ShortUsage: "env [flags] ", - ShortHelp: "`env` prints Gno environment information", + ShortHelp: "print gno environment information", }, c, func(_ context.Context, args []string) error { diff --git a/gnovm/cmd/gno/fmt.go b/gnovm/cmd/gno/fmt.go index de6c28c4df0..e37c50a079f 100644 --- a/gnovm/cmd/gno/fmt.go +++ b/gnovm/cmd/gno/fmt.go @@ -35,7 +35,7 @@ func newFmtCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "fmt", ShortUsage: "gno fmt [flags] [path ...]", - ShortHelp: "Run gno file formatter.", + ShortHelp: "gnofmt (reformat) package sources", LongHelp: "The `gno fmt` tool processes, formats, and cleans up `gno` source files.", }, cfg, diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index 7a5799f2835..b18e610d535 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -16,33 +16,33 @@ func main() { func newGnocliCmd(io commands.IO) *commands.Command { cmd := commands.NewCommand( commands.Metadata{ - ShortUsage: " [flags] [...]", - LongHelp: "Runs the gno development toolkit", + ShortUsage: "gno [arguments]", }, commands.NewEmptyConfig(), commands.HelpExec, ) cmd.AddSubCommands( - newModCmd(io), - newTestCmd(io), - newLintCmd(io), - newRunCmd(io), - newTranspileCmd(io), + newBugCmd(io), + // build newCleanCmd(io), - newReplCmd(), newDocCmd(io), newEnvCmd(io), - newBugCmd(io), + // fix newFmtCmd(io), - // graph - // vendor -- download deps from the chain in vendor/ - // list -- list packages - // render -- call render()? - // publish/release // generate - // "vm" -- starts an in-memory chain that can be interacted with? + // get + // install + // list -- list packages + newModCmd(io), + // work + newRunCmd(io), + // telemetry + newTestCmd(io), + newToolCmd(io), // version -- show cmd/gno, golang versions + newGnoVersionCmd(io), + // vet ) return cmd diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go index 5479d934ce6..e394684561f 100644 --- a/gnovm/cmd/gno/mod.go +++ b/gnovm/cmd/gno/mod.go @@ -25,7 +25,7 @@ func newModCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "mod", ShortUsage: "mod ", - ShortHelp: "manage gno.mod", + ShortHelp: "module maintenance", }, commands.NewEmptyConfig(), commands.HelpExec, @@ -33,8 +33,12 @@ func newModCmd(io commands.IO) *commands.Command { cmd.AddSubCommands( newModDownloadCmd(io), + // edit + newModGraphCmd(io), newModInitCmd(), newModTidy(io), + // vendor + // verify newModWhy(io), ) @@ -57,6 +61,21 @@ func newModDownloadCmd(io commands.IO) *commands.Command { ) } +func newModGraphCmd(io commands.IO) *commands.Command { + cfg := &modGraphCfg{} + return commands.NewCommand( + commands.Metadata{ + Name: "graph", + ShortUsage: "graph [path]", + ShortHelp: "print module requirement graph", + }, + cfg, + func(_ context.Context, args []string) error { + return execModGraph(cfg, args, io) + }, + ) +} + func newModInitCmd() *commands.Command { return commands.NewCommand( commands.Metadata{ @@ -140,6 +159,38 @@ func (c *modDownloadCfg) RegisterFlags(fs *flag.FlagSet) { ) } +type modGraphCfg struct{} + +func (c *modGraphCfg) RegisterFlags(fs *flag.FlagSet) { + // /out std + // /out remote + // /out _test processing + // ... +} + +func execModGraph(cfg *modGraphCfg, args []string, io commands.IO) error { + // default to current directory if no args provided + if len(args) == 0 { + args = []string{"."} + } + if len(args) > 1 { + return flag.ErrHelp + } + + stdout := io.Out() + + pkgs, err := gnomod.ListPkgs(args[0]) + if err != nil { + return err + } + for _, pkg := range pkgs { + for _, dep := range pkg.Imports { + fmt.Fprintf(stdout, "%s %s\n", pkg.Name, dep) + } + } + return nil +} + func execModDownload(cfg *modDownloadCfg, args []string, io commands.IO) error { if len(args) > 0 { return flag.ErrHelp diff --git a/gnovm/cmd/gno/mod_test.go b/gnovm/cmd/gno/mod_test.go index afce25597cd..6ea94f10839 100644 --- a/gnovm/cmd/gno/mod_test.go +++ b/gnovm/cmd/gno/mod_test.go @@ -62,7 +62,7 @@ func TestModApp(t *testing.T) { args: []string{"mod", "download"}, testDir: "../../tests/integ/replace_with_module", simulateExternalRepo: true, - stderrShouldContain: "gno: downloading gno.land/p/demo/users", + stderrShouldContain: "gno: downloading gno.land/p/demo/seqid", }, { args: []string{"mod", "download"}, @@ -210,6 +210,34 @@ func TestModApp(t *testing.T) { # gno.land/p/demo/avl valid.gno +`, + }, + + // test `gno mod graph` + { + args: []string{"mod", "graph"}, + testDir: "../../tests/integ/minimalist_gnomod", + simulateExternalRepo: true, + stdoutShouldBe: ``, + }, + { + args: []string{"mod", "graph"}, + testDir: "../../tests/integ/valid1", + simulateExternalRepo: true, + stdoutShouldBe: ``, + }, + { + args: []string{"mod", "graph"}, + testDir: "../../tests/integ/valid2", + simulateExternalRepo: true, + stdoutShouldBe: `gno.land/p/integ/valid gno.land/p/demo/avl +`, + }, + { + args: []string{"mod", "graph"}, + testDir: "../../tests/integ/require_remote_module", + simulateExternalRepo: true, + stdoutShouldBe: `gno.land/tests/importavl gno.land/p/demo/avl `, }, } diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go index 9a9beac5cd1..160178e349a 100644 --- a/gnovm/cmd/gno/run.go +++ b/gnovm/cmd/gno/run.go @@ -32,7 +32,7 @@ func newRunCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "run", ShortUsage: "run [flags] [...]", - ShortHelp: "runs the specified gno files", + ShortHelp: "run gno packages", }, cfg, func(_ context.Context, args []string) error { @@ -92,9 +92,9 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { stderr := io.Err() // init store and machine + output := test.OutputWithError(stdout, stderr) _, testStore := test.Store( - cfg.rootDir, false, - stdin, stdout, stderr) + cfg.rootDir, output) if cfg.verbose { testStore.SetLogStoreOps(true) } @@ -118,7 +118,7 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error { ctx := test.Context(pkgPath, send) m := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: pkgPath, - Output: stdout, + Output: output, Input: stdin, Store: testStore, Context: ctx, @@ -195,10 +195,10 @@ func runExpr(m *gno.Machine, expr string) { if r := recover(); r != nil { switch r := r.(type) { case gno.UnhandledPanicError: - fmt.Printf("panic running expression %s: %v\nStacktrace: %s\n", + fmt.Printf("panic running expression %s: %v\nStacktrace:\n%s\n", expr, r.Error(), m.ExceptionsStacktrace()) default: - fmt.Printf("panic running expression %s: %v\nMachine State:%s\nStacktrace: %s\n", + fmt.Printf("panic running expression %s: %v\nMachine State:%s\nStacktrace:\n%s\n", expr, r, m.String(), m.Stacktrace().String()) } panic(r) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index ea06b25d8e2..27f9c9b9302 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -26,6 +26,8 @@ type testCfg struct { updateGoldenTests bool printRuntimeMetrics bool printEvents bool + debug bool + debugAddr string } func newTestCmd(io commands.IO) *commands.Command { @@ -35,7 +37,7 @@ func newTestCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "test", ShortUsage: "test [flags] [...]", - ShortHelp: "runs the tests for the specified packages", + ShortHelp: "test packages", LongHelp: `Runs the tests for the specified packages. 'gno test' recompiles each package along with any files with names matching the @@ -143,6 +145,20 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) { false, "print emitted events", ) + + fs.BoolVar( + &c.debug, + "debug", + false, + "enable interactive debugger using stdin and stdout", + ) + + fs.StringVar( + &c.debugAddr, + "debug-addr", + "", + "enable interactive debugger using tcp address in the form [host]:port", + ) } func execTest(cfg *testCfg, args []string, io commands.IO) error { @@ -183,12 +199,13 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if cfg.verbose { stdout = io.Out() } - opts := test.NewTestOptions(cfg.rootDir, io.In(), stdout, io.Err()) + opts := test.NewTestOptions(cfg.rootDir, stdout, io.Err()) opts.RunFlag = cfg.run opts.Sync = cfg.updateGoldenTests opts.Verbose = cfg.verbose opts.Metrics = cfg.printRuntimeMetrics opts.Events = cfg.printEvents + opts.Debug = cfg.debug buildErrCount := 0 testErrCount := 0 @@ -213,10 +230,24 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { memPkg := gno.MustReadMemPackage(pkg.Dir, gnoPkgPath) + var hasError bool + startedAt := time.Now() - hasError := catchRuntimeError(gnoPkgPath, io.Err(), func() { + runtimeError := catchRuntimeError(gnoPkgPath, io.Err(), func() { + if modfile == nil || !modfile.Draft { + foundErr, lintErr := lintTypeCheck(io, memPkg, opts.TestStore) + if lintErr != nil { + io.ErrPrintln(lintErr) + hasError = true + } else if foundErr { + hasError = true + } + } else if cfg.verbose { + io.ErrPrintfln("%s: module is draft, skipping type check", gnoPkgPath) + } err = test.Test(memPkg, pkg.Dir, opts) }) + hasError = hasError || runtimeError duration := time.Since(startedAt) dstr := fmtDuration(duration) @@ -225,9 +256,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if err != nil { io.ErrPrintfln("%s: test pkg: %v", pkg.Dir, err) } - io.ErrPrintfln("FAIL") io.ErrPrintfln("FAIL %s \t%s", pkg.Dir, dstr) - io.ErrPrintfln("FAIL") testErrCount++ } else { io.ErrPrintfln("ok %s \t%s", pkg.Dir, dstr) diff --git a/gnovm/cmd/gno/testdata/lint/bad_import.txtar b/gnovm/cmd/gno/testdata/lint/bad_import.txtar index e2c0431443c..117a699fa6c 100644 --- a/gnovm/cmd/gno/testdata/lint/bad_import.txtar +++ b/gnovm/cmd/gno/testdata/lint/bad_import.txtar @@ -1,6 +1,6 @@ -# testing gno lint command: bad import error +# testing gno tool lint command: bad import error -! gno lint ./bad_file.gno +! gno tool lint ./bad_file.gno cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/file_error.txtar b/gnovm/cmd/gno/testdata/lint/file_error.txtar index 4fa50c6da81..f3301b46757 100644 --- a/gnovm/cmd/gno/testdata/lint/file_error.txtar +++ b/gnovm/cmd/gno/testdata/lint/file_error.txtar @@ -1,6 +1,6 @@ -# gno lint: test file error +# gno tool lint: test file error -! gno lint ./i_have_error_test.gno +! gno tool lint ./i_have_error_test.gno cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/no_error.txtar b/gnovm/cmd/gno/testdata/lint/no_error.txtar index 5dd3b164952..5033e054ac6 100644 --- a/gnovm/cmd/gno/testdata/lint/no_error.txtar +++ b/gnovm/cmd/gno/testdata/lint/no_error.txtar @@ -1,6 +1,6 @@ -# testing simple gno lint command with any error +# testing simple gno tool lint command with any error -gno lint ./good_file.gno +gno tool lint ./good_file.gno cmp stdout stdout.golden cmp stdout stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar index b5a046a7095..4564f2d9fff 100644 --- a/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar +++ b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar @@ -1,6 +1,6 @@ -# gno lint: no gnomod +# gno tool lint: no gnomod -! gno lint . +! gno tool lint . cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/lint/not_declared.txtar b/gnovm/cmd/gno/testdata/lint/not_declared.txtar index ac56b27e0df..3c3c304797e 100644 --- a/gnovm/cmd/gno/testdata/lint/not_declared.txtar +++ b/gnovm/cmd/gno/testdata/lint/not_declared.txtar @@ -1,6 +1,6 @@ -# testing gno lint command: not declared error +# testing gno tool lint command: not declared error -! gno lint ./bad_file.gno +! gno tool lint ./bad_file.gno cmp stdout stdout.golden cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/test/error_correct.txtar b/gnovm/cmd/gno/testdata/test/error_correct.txtar index f9ce4dd9028..bcd2c87da5c 100644 --- a/gnovm/cmd/gno/testdata/test/error_correct.txtar +++ b/gnovm/cmd/gno/testdata/test/error_correct.txtar @@ -3,8 +3,8 @@ gno test -v . stderr '=== RUN file/x_filetest.gno' -stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/x_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' -- x_filetest.gno -- package main diff --git a/gnovm/cmd/gno/testdata/test/filetest_events.txtar b/gnovm/cmd/gno/testdata/test/filetest_events.txtar index 34da5fe2ff0..87f873980d5 100644 --- a/gnovm/cmd/gno/testdata/test/filetest_events.txtar +++ b/gnovm/cmd/gno/testdata/test/filetest_events.txtar @@ -3,14 +3,14 @@ gno test -print-events . ! stdout .+ -stderr 'ok \. \d\.\d\ds' +stderr 'ok \. \d+\.\d\ds' gno test -print-events -v . stdout 'test' stderr '=== RUN file/valid_filetest.gno' -stderr '--- PASS: file/valid_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/valid_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/testdata/test/issue_2763.txtar b/gnovm/cmd/gno/testdata/test/issue_2763.txtar new file mode 100644 index 00000000000..9e98ace4860 --- /dev/null +++ b/gnovm/cmd/gno/testdata/test/issue_2763.txtar @@ -0,0 +1,35 @@ +# https://github.com/gnolang/gno/issues/2763 + +! gno test . +stderr 'redeclarations for identifiers.*add2' + +-- foo.gno -- +package foo + +func Add(a, b int) int { + return add2(a, b) +} + +func add2(a, b int) int { + return a + b +} + +-- foo_test.gno -- +package foo + +import ( + "testing" +) + +func TestFoo(t *testing.T) { + a := 1 + b := 2 + + if Add(a, b) != 3 { + t.Errorf("Add(%d, %d) = %d, want %d", a, b, Add(a, b), a+b) + } +} + +func add2(a, b int) int { + return a + b +} diff --git a/gnovm/cmd/gno/testdata/test/lint_error.txtar b/gnovm/cmd/gno/testdata/test/lint_error.txtar new file mode 100644 index 00000000000..ff6bf3d6018 --- /dev/null +++ b/gnovm/cmd/gno/testdata/test/lint_error.txtar @@ -0,0 +1,25 @@ +# Test with a valid _test.gno file + +! gno test -v . + +stdout 'hello123' +stderr 'PASS: TestAlwaysValid' +stderr 'declared and not used: x' +stderr 'FAIL' + +-- valid.gno -- +package valid + +func fn() { + x := 1 + println("hello123") +} + +-- valid_test.gno -- +package valid + +import "testing" + +func TestAlwaysValid(t *testing.T) { + fn() +} diff --git a/gnovm/cmd/gno/testdata/test/minim2.txtar b/gnovm/cmd/gno/testdata/test/minim2.txtar index 3c4d1d085f0..d66d5076ef0 100644 --- a/gnovm/cmd/gno/testdata/test/minim2.txtar +++ b/gnovm/cmd/gno/testdata/test/minim2.txtar @@ -2,8 +2,8 @@ gno test . -! stdout .+ -stderr 'ok \. \d\.\d\ds' +! stdout .+ +stderr 'ok \. \d+\.\d\ds' -- minim.gno -- package minim diff --git a/gnovm/cmd/gno/testdata/test/minim3.txtar b/gnovm/cmd/gno/testdata/test/minim3.txtar index ac8ae0c41d4..ba1847a21df 100644 --- a/gnovm/cmd/gno/testdata/test/minim3.txtar +++ b/gnovm/cmd/gno/testdata/test/minim3.txtar @@ -2,8 +2,8 @@ gno test . -! stdout .+ -stderr 'ok \. \d\.\d\ds' +! stdout .+ +stderr 'ok \. \d+\.\d\ds' -- minim.gno -- package minim diff --git a/gnovm/cmd/gno/testdata/test/multitest_events.txtar b/gnovm/cmd/gno/testdata/test/multitest_events.txtar index 321c790561a..5cb134f46a1 100644 --- a/gnovm/cmd/gno/testdata/test/multitest_events.txtar +++ b/gnovm/cmd/gno/testdata/test/multitest_events.txtar @@ -2,10 +2,10 @@ gno test -print-events . -! stdout .+ +! stdout .+ stderr 'EVENTS: \[{\"type\":\"EventA\",\"attrs\":\[\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestA\"}\]' stderr 'EVENTS: \[{\"type\":\"EventB\",\"attrs\":\[{\"key\":\"keyA\",\"value\":\"valA\"}\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestB\"},{\"type\":\"EventC\",\"attrs\":\[{\"key\":\"keyD\",\"value\":\"valD\"}\],\"pkg_path\":\"gno.land/r/.*\",\"func\":\"TestB\"}\]' -stderr 'ok \. \d\.\d\ds' +stderr 'ok \. \d+\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/testdata/test/output_correct.txtar b/gnovm/cmd/gno/testdata/test/output_correct.txtar index a8aa878e0a4..3a829e66bee 100644 --- a/gnovm/cmd/gno/testdata/test/output_correct.txtar +++ b/gnovm/cmd/gno/testdata/test/output_correct.txtar @@ -5,8 +5,8 @@ gno test -v . stdout 'hey' stdout 'hru?' stderr '=== RUN file/x_filetest.gno' -stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/x_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' -- x_filetest.gno -- package main diff --git a/gnovm/cmd/gno/testdata/test/output_sync.txtar b/gnovm/cmd/gno/testdata/test/output_sync.txtar index 45385a7eef9..1d701cc1c7f 100644 --- a/gnovm/cmd/gno/testdata/test/output_sync.txtar +++ b/gnovm/cmd/gno/testdata/test/output_sync.txtar @@ -6,8 +6,8 @@ stdout 'hey' stdout '^hru\?' stderr '=== RUN file/x_filetest.gno' -stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/x_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' cmp x_filetest.gno x_filetest.gno.golden diff --git a/gnovm/cmd/gno/testdata/test/pkg_underscore_test.txtar b/gnovm/cmd/gno/testdata/test/pkg_underscore_test.txtar index 7d204bdb98d..f1eb6e19af5 100644 --- a/gnovm/cmd/gno/testdata/test/pkg_underscore_test.txtar +++ b/gnovm/cmd/gno/testdata/test/pkg_underscore_test.txtar @@ -1,8 +1,9 @@ # Test a pkg name with _test as suffix # Set up GNOROOT in the current directory. -mkdir $WORK/gnovm +mkdir $WORK/gnovm/tests symlink $WORK/gnovm/stdlibs -> $GNOROOT/gnovm/stdlibs +symlink $WORK/gnovm/tests/stdlibs -> $GNOROOT/gnovm/tests/stdlibs env GNOROOT=$WORK gno test -v ./examples/gno.land/p/demo/hello diff --git a/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar b/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar index 9d935df74c2..0d6ffd24a92 100644 --- a/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar @@ -1,6 +1,7 @@ # Set up GNOROOT in the current directory. -mkdir $WORK/gnovm +mkdir $WORK/gnovm/tests symlink $WORK/gnovm/stdlibs -> $GNOROOT/gnovm/stdlibs +symlink $WORK/gnovm/tests/stdlibs -> $GNOROOT/gnovm/tests/stdlibs env GNOROOT=$WORK gno test -v ./examples/gno.land/r/demo/realm2 diff --git a/gnovm/cmd/gno/testdata/test/realm_correct.txtar b/gnovm/cmd/gno/testdata/test/realm_correct.txtar index ae1212133fd..8b1478d0df7 100644 --- a/gnovm/cmd/gno/testdata/test/realm_correct.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_correct.txtar @@ -4,8 +4,8 @@ gno test -v . ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' -stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/x_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' -- x_filetest.gno -- // PKGPATH: gno.land/r/xx @@ -18,4 +18,4 @@ func main() { } // Realm: -// switchrealm["gno.land/r/xx"] \ No newline at end of file +// switchrealm["gno.land/r/xx"] diff --git a/gnovm/cmd/gno/testdata/test/realm_sync.txtar b/gnovm/cmd/gno/testdata/test/realm_sync.txtar index 65a930b2f03..91c83235d15 100644 --- a/gnovm/cmd/gno/testdata/test/realm_sync.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_sync.txtar @@ -4,8 +4,8 @@ gno test -v . -update-golden-tests ! stdout .+ # stdout should be empty stderr '=== RUN file/x_filetest.gno' -stderr '--- PASS: file/x_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/x_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' cmp x_filetest.gno x_filetest.gno.golden diff --git a/gnovm/cmd/gno/testdata/test/two_packages_init.txtar b/gnovm/cmd/gno/testdata/test/two_packages_init.txtar new file mode 100644 index 00000000000..280fd1d529e --- /dev/null +++ b/gnovm/cmd/gno/testdata/test/two_packages_init.txtar @@ -0,0 +1,46 @@ +# Test that two packages have independent states. +# https://github.com/gnolang/gno/issues/3240 + +# Set up GNOROOT in the current directory. +mkdir $WORK/gnovm/tests +symlink $WORK/gnovm/stdlibs -> $GNOROOT/gnovm/stdlibs +symlink $WORK/gnovm/tests/stdlibs -> $GNOROOT/gnovm/tests/stdlibs +env GNOROOT=$WORK + +gno test -v ./examples/gno.land/r/demo/... + +-- examples/gno.land/r/demo/aa/a.gno -- +package aa + +import "gno.land/r/demo/bb" + +func init() { + bb.Call() +} + +-- examples/gno.land/r/demo/aa/a_test.gno -- +package aa + +import "testing" + +func TestA(t *testing.T) {} + +-- examples/gno.land/r/demo/bb/a.gno -- +package bb + +var called int + +func Call() { + called++ +} + +-- examples/gno.land/r/demo/bb/a_test.gno -- +package bb + +import "testing" + +func TestCalled(t *testing.T) { + if called != 0 { + t.Fatalf("called: %v", called) + } +} diff --git a/gnovm/cmd/gno/testdata/test/valid_filetest.txtar b/gnovm/cmd/gno/testdata/test/valid_filetest.txtar index 4e24ad9ab08..bd73ce3dc99 100644 --- a/gnovm/cmd/gno/testdata/test/valid_filetest.txtar +++ b/gnovm/cmd/gno/testdata/test/valid_filetest.txtar @@ -3,14 +3,14 @@ gno test . ! stdout .+ -stderr 'ok \. \d\.\d\ds' +stderr 'ok \. \d+\.\d\ds' gno test -v . stdout 'test' stderr '=== RUN file/valid_filetest.gno' -stderr '--- PASS: file/valid_filetest.gno \(\d\.\d\ds\)' -stderr 'ok \. \d\.\d\ds' +stderr '--- PASS: file/valid_filetest.gno \(\d+\.\d\ds\)' +stderr 'ok \. \d+\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/testdata/test/valid_test.txtar b/gnovm/cmd/gno/testdata/test/valid_test.txtar index 9590626776c..5a9fe37a4b0 100644 --- a/gnovm/cmd/gno/testdata/test/valid_test.txtar +++ b/gnovm/cmd/gno/testdata/test/valid_test.txtar @@ -2,13 +2,13 @@ gno test . -! stdout .+ -stderr 'ok \. \d\.\d\ds' +! stdout .+ +stderr 'ok \. \d+\.\d\ds' gno test ./... -! stdout .+ -stderr 'ok \. \d\.\d\ds' +! stdout .+ +stderr 'ok \. \d+\.\d\ds' -- valid.gno -- package valid diff --git a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar index 145fe796c09..0b41319a190 100644 --- a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar @@ -1,7 +1,7 @@ -# Run gno transpile with -gobuild flag +# Run gno tool transpile with -gobuild flag # The error messages changed sometime in go1.23, so this avoids errors -! gno transpile -gobuild . +! gno tool transpile -gobuild . ! stdout .+ stderr '^main.gno:4:6: .*declared and not used' diff --git a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar index 629de2b7f3d..1008d45adb2 100644 --- a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with -gobuild flag on file with parse error +# Run gno tool transpile with -gobuild flag on file with parse error -! gno transpile -gobuild . +! gno tool transpile -gobuild . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' diff --git a/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar index 0c51012feb7..23e9e805fe5 100644 --- a/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar +++ b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with gno files with an invalid import path +# Run gno tool transpile with gno files with an invalid import path -! gno transpile . +! gno tool transpile . ! stdout .+ stderr '^main.gno:5:2: import "xxx" does not exist$' diff --git a/gnovm/cmd/gno/testdata/transpile/no_args.txtar b/gnovm/cmd/gno/testdata/transpile/no_args.txtar index 6d8592d4e3b..30ea24fbf73 100644 --- a/gnovm/cmd/gno/testdata/transpile/no_args.txtar +++ b/gnovm/cmd/gno/testdata/transpile/no_args.txtar @@ -1,6 +1,6 @@ -# Run gno transpile without args +# Run gno tool transpile without args -! gno transpile +! gno tool transpile ! stdout .+ stderr 'USAGE' diff --git a/gnovm/cmd/gno/testdata/transpile/parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar index b94b86992af..c19276ef273 100644 --- a/gnovm/cmd/gno/testdata/transpile/parse_error.txtar +++ b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with gno files with parse errors +# Run gno tool transpile with gno files with parse errors -! gno transpile . +! gno tool transpile . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' diff --git a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar index 2bd1841d2b4..f9ccd8d8b53 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar @@ -1,6 +1,6 @@ -# Run gno transpile on an empty dir +# Run gno tool transpile on an empty dir -gno transpile . +gno tool transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar index 40bb1ecb98a..8e95c5994d0 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with -gobuild flag on an individual file +# Run gno tool transpile with -gobuild flag on an individual file -gno transpile -gobuild -v main.gno +gno tool transpile -gobuild -v main.gno ! stdout .+ cmp stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar index 2eacfb9de60..72ca7a4f2f4 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with -gobuild flag +# Run gno tool transpile with -gobuild flag -gno transpile -gobuild -v . +gno tool transpile -gobuild -v . ! stdout .+ cmp stderr stderr.golden @@ -12,7 +12,7 @@ cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden rm mai.gno.gen.gosub/sub.gno.gen.go # Re-try, but use an absolute path. -gno transpile -gobuild -v $WORK +gno tool transpile -gobuild -v $WORK ! stdout .+ cmpenv stderr stderr2.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar index b1a63890f46..62953e96fb7 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with valid gno files, using the -output flag. +# Run gno tool transpile with valid gno files, using the -output flag. -gno transpile -v -output directory/hello/ . +gno tool transpile -v -output directory/hello/ . ! stdout .+ cmp stderr stderr1.golden @@ -9,7 +9,7 @@ exists directory/hello/main.gno.gen.go rm directory # Try running using the absolute path to the directory. -gno transpile -v -output directory/hello $WORK +gno tool transpile -v -output directory/hello $WORK ! stdout .+ cmpenv stderr stderr2.golden @@ -20,7 +20,7 @@ rm directory # Try running in subdirectory, using a "relative non-local path." (ie. has "../") mkdir subdir cd subdir -gno transpile -v -output hello .. +gno tool transpile -v -output hello .. ! stdout .+ cmpenv stderr ../stderr3.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar index 3540e865f3e..c948e53ebb3 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with valid gno files, using the -output and -gobuild flags together. +# Run gno tool transpile with valid gno files, using the -output and -gobuild flags together. -gno transpile -v -output directory/hello/ -gobuild . +gno tool transpile -v -output directory/hello/ -gobuild . ! stdout .+ cmp stderr stderr1.golden @@ -9,7 +9,7 @@ exists directory/hello/main.gno.gen.go rm directory # Try running using the absolute path to the directory. -gno transpile -v -output directory/hello -gobuild $WORK +gno tool transpile -v -output directory/hello -gobuild $WORK ! stdout .+ cmpenv stderr stderr2.golden @@ -20,7 +20,7 @@ rm directory # Try running in subdirectory, using a "relative non-local path." (ie. has "../") mkdir subdir cd subdir -gno transpile -v -output hello -gobuild .. +gno tool transpile -v -output hello -gobuild .. ! stdout .+ cmpenv stderr ../stderr3.golden diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar index 86cc6f12f7a..31907052225 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar @@ -1,8 +1,8 @@ -# Run gno transpile with an individual file. +# Run gno tool transpile with an individual file. # Running transpile on the current directory should only precompile # main.gno. -gno transpile -v . +gno tool transpile -v . ! stdout .+ stderr ^\.$ @@ -12,7 +12,7 @@ exists main.gno.gen.go rm main.gno.gen.go # Running it using individual filenames should precompile hello_test.gno, as well. -gno transpile -v main.gno hello_test.gno +gno tool transpile -v main.gno hello_test.gno ! stdout .+ cmp stderr transpile-files-stderr.golden @@ -36,7 +36,7 @@ package main import "std" func hello() { - std.AssertOriginCall() + std.ChainID() } -- main.gno.gen.go.golden -- @@ -61,5 +61,5 @@ package main import "github.com/gnolang/gno/gnovm/stdlibs/std" func hello() { - std.AssertOriginCall(nil) + std.ChainID(nil) } diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar index 2a24423598a..74923b9846a 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar @@ -1,6 +1,6 @@ -# Run gno transpile with valid gno files +# Run gno tool transpile with valid gno files -gno transpile . +gno tool transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar index a765ab5093b..e0c42d986e4 100644 --- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar +++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar @@ -1,7 +1,7 @@ -# Run gno transpile with dependencies +# Run gno tool transpile with dependencies env GNOROOT=$WORK -gno transpile -v ./examples +gno tool transpile -v ./examples ! stdout .+ cmpenv stderr stderr.golden diff --git a/gnovm/cmd/gno/testdata_test.go b/gnovm/cmd/gno/testdata_test.go index c5cb0def04e..86f356f2ebb 100644 --- a/gnovm/cmd/gno/testdata_test.go +++ b/gnovm/cmd/gno/testdata_test.go @@ -24,7 +24,6 @@ func Test_Scripts(t *testing.T) { } name := dir.Name() - t.Logf("testing: %s", name) t.Run(name, func(t *testing.T) { testdir := filepath.Join(testdata, name) p := integration.NewTestingParams(t, testdir) diff --git a/gnovm/cmd/gno/tool.go b/gnovm/cmd/gno/tool.go new file mode 100644 index 00000000000..0e4f7ff51d7 --- /dev/null +++ b/gnovm/cmd/gno/tool.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/gnolang/gno/tm2/pkg/commands" +) + +func newToolCmd(io commands.IO) *commands.Command { + cmd := commands.NewCommand( + commands.Metadata{ + Name: "tool", + ShortUsage: "gno tool command [args...]", + ShortHelp: "run specified gno tool", + }, + commands.NewEmptyConfig(), + commands.HelpExec, + ) + + cmd.AddSubCommands( + // go equivalent commands: + // + // compile + // transpile + // pprof + // trace + // vet + + // gno specific commands: + // + // ast + newLintCmd(io), + // publish/release + // render -- call render()? + newReplCmd(), + newTranspileCmd(io), + // "vm" -- starts an in-memory chain that can be interacted with? + ) + + return cmd +} diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/tool_lint.go similarity index 92% rename from gnovm/cmd/gno/lint.go rename to gnovm/cmd/gno/tool_lint.go index ce3465b484e..69d4f34eb10 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/tool_lint.go @@ -97,9 +97,9 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { hasError := false - bs, ts := test.Store( - rootDir, false, - nopReader{}, goio.Discard, goio.Discard, + bs, ts := test.StoreWithOptions( + rootDir, goio.Discard, + test.StoreOptions{PreprocessOnly: true}, ) for _, pkgPath := range pkgPaths { @@ -159,16 +159,14 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { io.ErrPrintfln("%s: module is draft, skipping type check", pkgPath) } - tm := test.Machine(gs, goio.Discard, memPkg.Path) - defer tm.Release() + tm := test.Machine(gs, goio.Discard, memPkg.Path, false) - // Check package - tm.RunMemPackage(memPkg, true) + defer tm.Release() // Check test files - testFiles := lintTestFiles(memPkg) + packageFiles := sourceAndTestFileset(memPkg) - tm.RunFiles(testFiles.Files...) + tm.PreprocessFiles(memPkg.Name, memPkg.Path, packageFiles, false, false) }) if hasRuntimeErr { hasError = true @@ -221,20 +219,21 @@ func lintTypeCheck(io commands.IO, memPkg *gnovm.MemPackage, testStore gno.Store return true, nil } -func lintTestFiles(memPkg *gnovm.MemPackage) *gno.FileSet { +func sourceAndTestFileset(memPkg *gnovm.MemPackage) *gno.FileSet { testfiles := &gno.FileSet{} for _, mfile := range memPkg.Files { if !strings.HasSuffix(mfile.Name, ".gno") { continue // Skip non-GNO files } - n, _ := gno.ParseFile(mfile.Name, mfile.Body) + n := gno.MustParseFile(mfile.Name, mfile.Body) if n == nil { continue // Skip empty files } // XXX: package ending with `_test` is not supported yet - if strings.HasSuffix(mfile.Name, "_test.gno") && !strings.HasSuffix(string(n.PkgName), "_test") { + if !strings.HasSuffix(mfile.Name, "_filetest.gno") && + !strings.HasSuffix(string(n.PkgName), "_test") { // Keep only test files testfiles.AddFiles(n) } @@ -318,7 +317,3 @@ func issueFromError(pkgPath string, err error) lintIssue { } return issue } - -type nopReader struct{} - -func (nopReader) Read(p []byte) (int, error) { return 0, goio.EOF } diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/tool_lint_test.go similarity index 56% rename from gnovm/cmd/gno/lint_test.go rename to gnovm/cmd/gno/tool_lint_test.go index 4589fc55f92..3f9e5cd59ba 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/tool_lint_test.go @@ -8,51 +8,65 @@ import ( func TestLintApp(t *testing.T) { tc := []testMainCase{ { - args: []string{"lint"}, + args: []string{"tool", "lint"}, errShouldBe: "flag: help requested", - }, { - args: []string{"lint", "../../tests/integ/run_main/"}, + }, + { + args: []string{"tool", "lint", "../../tests/integ/run_main/"}, stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)", errShouldBe: "exit code: 1", - }, { - args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, + }, + { + args: []string{"tool", "lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, stderrShouldContain: "undefined_variables_test.gno:6:28: name toto not declared (code=2)", errShouldBe: "exit code: 1", - }, { - args: []string{"lint", "../../tests/integ/package_not_declared/main.gno"}, + }, + { + args: []string{"tool", "lint", "../../tests/integ/package_not_declared/main.gno"}, stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2)", errShouldBe: "exit code: 1", - }, { - args: []string{"lint", "../../tests/integ/several-lint-errors/main.gno"}, - stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2)\n../../tests/integ/several-lint-errors/main.gno:6", + }, + { + args: []string{"tool", "lint", "../../tests/integ/several-lint-errors/main.gno"}, + stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=3)\n../../tests/integ/several-lint-errors/main.gno:6", errShouldBe: "exit code: 1", - }, { - args: []string{"lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, + }, + { + args: []string{"tool", "lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, stderrShouldContain: func() string { lines := []string{ - "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2)", - "../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2)", - "../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2)", - "../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2)", + "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=3)", + "../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=3)", + "../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=3)", + "../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=3)", } return strings.Join(lines, "\n") + "\n" }(), errShouldBe: "exit code: 1", - }, { - args: []string{"lint", "../../tests/integ/minimalist_gnomod/"}, + }, + { + args: []string{"tool", "lint", "../../tests/integ/minimalist_gnomod/"}, // TODO: raise an error because there is a gno.mod, but no .gno files - }, { - args: []string{"lint", "../../tests/integ/invalid_module_name/"}, + }, + { + args: []string{"tool", "lint", "../../tests/integ/invalid_module_name/"}, // TODO: raise an error because gno.mod is invalid - }, { - args: []string{"lint", "../../tests/integ/invalid_gno_file/"}, + }, + { + args: []string{"tool", "lint", "../../tests/integ/invalid_gno_file/"}, stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=2)", errShouldBe: "exit code: 1", - }, { - args: []string{"lint", "../../tests/integ/typecheck_missing_return/"}, + }, + { + args: []string{"tool", "lint", "../../tests/integ/typecheck_missing_return/"}, stderrShouldContain: "../../tests/integ/typecheck_missing_return/main.gno:5:1: missing return (code=4)", errShouldBe: "exit code: 1", }, + { + args: []string{"tool", "lint", "../../tests/integ/init/"}, + // stderr / stdout should be empty; the init function and statements + // should not be executed + }, // TODO: 'gno mod' is valid? // TODO: are dependencies valid? diff --git a/gnovm/cmd/gno/repl.go b/gnovm/cmd/gno/tool_repl.go similarity index 100% rename from gnovm/cmd/gno/repl.go rename to gnovm/cmd/gno/tool_repl.go diff --git a/gnovm/cmd/gno/repl_test.go b/gnovm/cmd/gno/tool_repl_test.go similarity index 100% rename from gnovm/cmd/gno/repl_test.go rename to gnovm/cmd/gno/tool_repl_test.go diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/tool_transpile.go similarity index 100% rename from gnovm/cmd/gno/transpile.go rename to gnovm/cmd/gno/tool_transpile.go diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/tool_transpile_test.go similarity index 100% rename from gnovm/cmd/gno/transpile_test.go rename to gnovm/cmd/gno/tool_transpile_test.go diff --git a/gnovm/cmd/gno/version.go b/gnovm/cmd/gno/version.go new file mode 100644 index 00000000000..f9b967d1c40 --- /dev/null +++ b/gnovm/cmd/gno/version.go @@ -0,0 +1,24 @@ +package main + +import ( + "context" + + "github.com/gnolang/gno/gnovm/pkg/version" + "github.com/gnolang/gno/tm2/pkg/commands" +) + +// newVersionCmd creates a new version command +func newGnoVersionCmd(io commands.IO) *commands.Command { + return commands.NewCommand( + commands.Metadata{ + Name: "version", + ShortUsage: "version", + ShortHelp: "display installed gno version", + }, + nil, + func(_ context.Context, args []string) error { + io.Println("gno version:", version.Version) + return nil + }, + ) +} diff --git a/gnovm/cmd/gno/version_test.go b/gnovm/cmd/gno/version_test.go new file mode 100644 index 00000000000..fab47319297 --- /dev/null +++ b/gnovm/cmd/gno/version_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "testing" + + "github.com/gnolang/gno/gnovm/pkg/version" +) + +func TestVersionApp(t *testing.T) { + originalVersion := version.Version + + t.Cleanup(func() { + version.Version = originalVersion + }) + + versionValues := []string{"chain/test4.2", "develop", "master"} + + testCases := make([]testMainCase, len(versionValues)) + for i, v := range versionValues { + testCases[i] = testMainCase{ + args: []string{"version"}, + stdoutShouldContain: "gno version: " + v, + } + } + + for i, testCase := range testCases { + t.Run(versionValues[i], func(t *testing.T) { + version.Version = versionValues[i] + testMainCaseRun(t, []testMainCase{testCase}) + }) + } +} diff --git a/gnovm/memfile.go b/gnovm/memfile.go index 6988c893dd7..a08e89579ad 100644 --- a/gnovm/memfile.go +++ b/gnovm/memfile.go @@ -34,7 +34,7 @@ func (mempkg *MemPackage) GetFile(name string) *MemFile { } func (mempkg *MemPackage) IsEmpty() bool { - return len(mempkg.Files) == 0 + return mempkg.Name == "" || len(mempkg.Files) == 0 } const pathLengthLimit = 256 diff --git a/gnovm/pkg/benchops/gno/avl/node.gno b/gnovm/pkg/benchops/gno/avl/node.gno index 7308e163768..1a15ddd03b3 100644 --- a/gnovm/pkg/benchops/gno/avl/node.gno +++ b/gnovm/pkg/benchops/gno/avl/node.gno @@ -6,7 +6,7 @@ package avl // Node represents a node in an AVL tree. type Node struct { key string // key is the unique identifier for the node. - value interface{} // value is the data stored in the node. + value any // value is the data stored in the node. height int8 // height is the height of the node in the tree. size int // size is the number of nodes in the subtree rooted at this node. leftNode *Node // leftNode is the left child of the node. @@ -14,7 +14,7 @@ type Node struct { } // NewNode creates a new node with the given key and value. -func NewNode(key string, value interface{}) *Node { +func NewNode(key string, value any) *Node { return &Node{ key: key, value: value, @@ -42,7 +42,7 @@ func (node *Node) Key() string { } // Value returns the value of the node. -func (node *Node) Value() interface{} { +func (node *Node) Value() any { return node.value } @@ -79,7 +79,7 @@ func (node *Node) Has(key string) (has bool) { // Get searches for a node with the given key in the subtree rooted at the node // and returns its index, value, and whether it exists. -func (node *Node) Get(key string) (index int, value interface{}, exists bool) { +func (node *Node) Get(key string) (index int, value any, exists bool) { if node == nil { return 0, nil, false } @@ -106,7 +106,7 @@ func (node *Node) Get(key string) (index int, value interface{}, exists bool) { // GetByIndex retrieves the key-value pair of the node at the given index // in the subtree rooted at the node. -func (node *Node) GetByIndex(index int) (key string, value interface{}) { +func (node *Node) GetByIndex(index int) (key string, value any) { if node.height == 0 { if index == 0 { return node.key, node.value @@ -125,7 +125,7 @@ func (node *Node) GetByIndex(index int) (key string, value interface{}) { // and returns the new root of the subtree and whether an existing node was updated. // // XXX consider a better way to do this... perhaps split Node from Node. -func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) { +func (node *Node) Set(key string, value any) (newSelf *Node, updated bool) { if node == nil { return NewNode(key, value), false } @@ -151,7 +151,7 @@ func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated boo // setLeaf inserts a new leaf node with the given key-value pair into the subtree rooted at the node, // and returns the new root of the subtree and whether an existing node was updated. -func (node *Node) setLeaf(key string, value interface{}) (newSelf *Node, updated bool) { +func (node *Node) setLeaf(key string, value any) (newSelf *Node, updated bool) { if key == node.key { return NewNode(key, value), true } @@ -179,7 +179,7 @@ func (node *Node) setLeaf(key string, value interface{}) (newSelf *Node, updated // returns the new root of the subtree, the new leftmost leaf key (if changed), // the removed value and the removal was successful. func (node *Node) Remove(key string) ( - newNode *Node, newKey string, value interface{}, removed bool, + newNode *Node, newKey string, value any, removed bool, ) { if node == nil { return nil, "", nil, false diff --git a/gnovm/pkg/benchops/gno/avl/tree.gno b/gnovm/pkg/benchops/gno/avl/tree.gno index e7aa55eb7e4..29d32c07a3d 100644 --- a/gnovm/pkg/benchops/gno/avl/tree.gno +++ b/gnovm/pkg/benchops/gno/avl/tree.gno @@ -1,6 +1,6 @@ package avl -type IterCbFn func(key string, value interface{}) bool +type IterCbFn func(key string, value any) bool //---------------------------------------- // Tree @@ -30,21 +30,21 @@ func (tree *Tree) Has(key string) (has bool) { // Get retrieves the value associated with the given key. // It returns the value and a boolean indicating whether the key exists. -func (tree *Tree) Get(key string) (value interface{}, exists bool) { +func (tree *Tree) Get(key string) (value any, exists bool) { _, value, exists = tree.node.Get(key) return } // GetByIndex retrieves the key-value pair at the specified index in the tree. // It returns the key and value at the given index. -func (tree *Tree) GetByIndex(index int) (key string, value interface{}) { +func (tree *Tree) GetByIndex(index int) (key string, value any) { return tree.node.GetByIndex(index) } // Set inserts a key-value pair into the tree. // If the key already exists, the value will be updated. // It returns a boolean indicating whether the key was newly inserted or updated. -func (tree *Tree) Set(key string, value interface{}) (updated bool) { +func (tree *Tree) Set(key string, value any) (updated bool) { newnode, updated := tree.node.Set(key, value) tree.node = newnode return updated @@ -52,7 +52,7 @@ func (tree *Tree) Set(key string, value interface{}) (updated bool) { // Remove removes a key-value pair from the tree. // It returns the removed value and a boolean indicating whether the key was found and removed. -func (tree *Tree) Remove(key string) (value interface{}, removed bool) { +func (tree *Tree) Remove(key string) (value any, removed bool) { newnode, _, value, removed := tree.node.Remove(key) tree.node = newnode return value, removed diff --git a/gnovm/pkg/benchops/gno/opcodes/opcode.gno b/gnovm/pkg/benchops/gno/opcodes/opcode.gno index 05a5f88b48d..e5b857d7c67 100644 --- a/gnovm/pkg/benchops/gno/opcodes/opcode.gno +++ b/gnovm/pkg/benchops/gno/opcodes/opcode.gno @@ -37,18 +37,18 @@ OpEval, (const (0 int)) OpEval, (const (1 int)) OpArrayLit, [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} OpDefine, a2 := [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} -OpExec, bodyStmt[0/0/2]=m := (const (make func(t type{},z ...interface{})( map[int]int)))(map[(const-type int)] (const-type int)) -OpEval, (const (make func(t type{},z ...interface{})( map[int]int)))(map[(const-type int)] (const-type int)) -OpEval, (const (make func(t type{},z ...interface{})( map[int]int))) +OpExec, bodyStmt[0/0/2]=m := (const (make func(t type{},z ...any)( map[int]int)))(map[(const-type int)] (const-type int)) +OpEval, (const (make func(t type{},z ...any)( map[int]int)))(map[(const-type int)] (const-type int)) +OpEval, (const (make func(t type{},z ...any)( map[int]int))) OpEval, map[(const-type int)] (const-type int) OpEval, (const-type int) OpEval, (const-type int) OpMapType, (typeval{int} type{}) -OpPreCall, (const (make func(t type{},z ...interface{})( map[int]int)))(map[(const-type int)] (const-type int)) +OpPreCall, (const (make func(t type{},z ...any)( map[int]int)))(map[(const-type int)] (const-type int)) OpCall, make OpCallNativeBody, make OpReturn, [FRAME FUNC:make RECV:(undefined) (1 args) 4/1/0/2/2 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] -OpDefine, m := (const (make func(t type{},z ...interface{})( map[int]int)))(map[(const-type int)] (const-type int)) +OpDefine, m := (const (make func(t type{},z ...any)( map[int]int)))(map[(const-type int)] (const-type int)) OpExec, bodyStmt[0/0/3]=s := [](const-type int){(const (0 int)), (const (1 int)), (const (2 int)), (const (3 int)), (const (4 int)), (const (5 int)), (const (6 int)), (const (7 int)), (const (8 int)), (const (9 int))} OpEval, [](const-type int){(const (0 int)), (const (1 int)), (const (2 int)), (const (3 int)), (const (4 int)), (const (5 int)), (const (6 int)), (const (7 int)), (const (8 int)), (const (9 int))} OpEval, [](const-type int) @@ -403,11 +403,11 @@ OpEval, func() OpFuncType, func() OpFuncLit, func func(){ a[(const (0 int))]++ } OpDefer, defer func func(){ a[(const (0 int))]++ }() -OpExec, bodyStmt[0/0/2]=defer (const (len func(x interface{})( int)))(a) -OpBody, defer (const (len func(x interface{})( int)))(a) -OpEval, (const (len func(x interface{})( int))) +OpExec, bodyStmt[0/0/2]=defer (const (len func(x any)( int)))(a) +OpBody, defer (const (len func(x any)( int)))(a) +OpEval, (const (len func(x any)( int))) OpEval, a -OpDefer, defer (const (len func(x interface{})( int)))(a) +OpDefer, defer (const (len func(x any)( int)))(a) OpExec, bodyStmt[0/0/3]=return a[(const (0 int))] OpBody, return a[(const (0 int))] OpEval, a[(const (0 int))] @@ -690,8 +690,8 @@ func OpLand() { } /* -OpEval, (const (recover func()(exception interface{}))) -OpDefer, defer (const (recover func()(exception interface{})))() +OpEval, (const (recover func()(exception any))) +OpDefer, defer (const (recover func()(exception any)))() OpExec, bodyStmt[0/0/1]=panic((const ("panic" string))) OpEval, (const ("panic" string)) OpPanic1 @@ -745,7 +745,7 @@ OpPopFrameAndReset, [FRAME LABEL: 4/2/0/3/3] OpRangeIter, bodyStmt[4/3/-1]=(init) OpRangeIter, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: } OpEval, v -OpTypeSwitch, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: }, (typeval{interface{} (0x1400bba9130)} type{}) +OpTypeSwitch, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: }, (typeval{any (0x1400bba9130)} type{}) OpPopBlock, Block(ID:0000000000000000000000000000000000000000:0,Addr:0x1400bbbcd20,Source:switch v { case (c...,Parent:0x1400bbbc5a0) OpPopFrameAndReset, [FRAME LABEL: 4/2/0/3/3] OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] @@ -755,7 +755,7 @@ OpReturnFromBlock, [FRAME FUNC:OpTypeSwitch RECV:(undefined) (0 args) 1/0/0/0/1 OpHalt */ func OpTypeSwitch() { - values := []interface{}{1, "hello", 1.0, interface{}} + values := []any{1, "hello", 1.0, any} for _, v := range values { switch v.(type) { case int: @@ -820,74 +820,74 @@ OpEval, one OpArrayLit, [(const (1 int))](const-type int){one} OpRef, &([(const (1 int))](const-type int){one}) OpDefine, p := &([(const (1 int))](const-type int){one}) -OpExec, bodyStmt[0/0/5]=for i := range a { (const (println func(xs ...interface{})()))(a[i]) } -OpBody, for i := range a { (const (println func(xs ...interface{})()))(a[i]) } +OpExec, bodyStmt[0/0/5]=for i := range a { (const (println func(xs ...any)()))(a[i]) } +OpBody, for i := range a { (const (println func(xs ...any)()))(a[i]) } OpEval, a OpExec, bodyStmt[0/0/-2]=(init) -OpRangeIter, (const (println func(xs ...interface{})()))(a[i]) -OpEval, (const (println func(xs ...interface{})()))(a[i]) -OpEval, (const (println func(xs ...interface{})())) +OpRangeIter, (const (println func(xs ...any)()))(a[i]) +OpEval, (const (println func(xs ...any)()))(a[i]) +OpEval, (const (println func(xs ...any)())) OpEval, a[i] OpEval, a OpEval, i OpIndex1, (array[(1 int)] [1]int) -OpPreCall, (const (println func(xs ...interface{})()))(a[i]) +OpPreCall, (const (println func(xs ...any)()))(a[i]) OpCall, println OpCallNativeBody, println OpReturn, [FRAME FUNC:println RECV:(undefined) (1 args) 5/2/0/3/3 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] OpPopResults OpExec, bodyStmt[1/0/1]=(end) OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] -OpExec, bodyStmt[0/0/6]=for i := range s { (const (println func(xs ...interface{})()))(s[i]) } -OpBody, for i := range s { (const (println func(xs ...interface{})()))(s[i]) } +OpExec, bodyStmt[0/0/6]=for i := range s { (const (println func(xs ...any)()))(s[i]) } +OpBody, for i := range s { (const (println func(xs ...any)()))(s[i]) } OpEval, s OpExec, bodyStmt[0/0/-2]=(init) -OpRangeIterString, (const (println func(xs ...interface{})()))(s[i]) -OpEval, (const (println func(xs ...interface{})()))(s[i]) -OpEval, (const (println func(xs ...interface{})())) +OpRangeIterString, (const (println func(xs ...any)()))(s[i]) +OpEval, (const (println func(xs ...any)()))(s[i]) +OpEval, (const (println func(xs ...any)())) OpEval, s[i] OpEval, s OpEval, i OpIndex1, ("h" string) -OpPreCall, (const (println func(xs ...interface{})()))(s[i]) +OpPreCall, (const (println func(xs ...any)()))(s[i]) OpCall, println OpCallNativeBody, println OpReturn, [FRAME FUNC:println RECV:(undefined) (1 args) 5/2/0/3/3 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] OpPopResults OpExec, bodyStmt[0/0/1]=(end) OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] -OpExec, bodyStmt[0/0/7]=for i := range m { (const (println func(xs ...interface{})()))(m[i]) } -OpBody, for i := range m { (const (println func(xs ...interface{})()))(m[i]) } +OpExec, bodyStmt[0/0/7]=for i := range m { (const (println func(xs ...any)()))(m[i]) } +OpBody, for i := range m { (const (println func(xs ...any)()))(m[i]) } OpEval, m OpExec, bodyStmt[0/0/-2]=(init) -OpRangeIterMap, (const (println func(xs ...interface{})()))(m[i]) -OpEval, (const (println func(xs ...interface{})()))(m[i]) -OpEval, (const (println func(xs ...interface{})())) +OpRangeIterMap, (const (println func(xs ...any)()))(m[i]) +OpEval, (const (println func(xs ...any)()))(m[i]) +OpEval, (const (println func(xs ...any)())) OpEval, m[i] OpEval, m OpEval, i OpIndex1, (map{(1 int):("one" string)} map[int]string) -OpPreCall, (const (println func(xs ...interface{})()))(m[i]) +OpPreCall, (const (println func(xs ...any)()))(m[i]) OpCall, println OpCallNativeBody, println OpReturn, [FRAME FUNC:println RECV:(undefined) (1 args) 5/2/0/3/3 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] OpPopResults OpExec, bodyStmt[0/0/1]=(end) OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] -OpExec, bodyStmt[0/0/8]=for i := range p { (const (println func(xs ...interface{})()))(*(p)[i]) } -OpBody, for i := range p { (const (println func(xs ...interface{})()))(*(p)[i]) } +OpExec, bodyStmt[0/0/8]=for i := range p { (const (println func(xs ...any)()))(*(p)[i]) } +OpBody, for i := range p { (const (println func(xs ...any)()))(*(p)[i]) } OpEval, p OpExec, bodyStmt[0/0/-2]=(init) -OpRangeIterArrayPtr, (const (println func(xs ...interface{})()))(*(p)[i]) -OpEval, (const (println func(xs ...interface{})()))(*(p)[i]) -OpEval, (const (println func(xs ...interface{})())) +OpRangeIterArrayPtr, (const (println func(xs ...any)()))(*(p)[i]) +OpEval, (const (println func(xs ...any)()))(*(p)[i]) +OpEval, (const (println func(xs ...any)())) OpEval, *(p)[i] OpEval, *(p) OpEval, p OpStar, (&0x1400a7b1590.(*[1]int) *[1]int) OpEval, i OpIndex1, (array[(1 int)] [1]int) -OpPreCall, (const (println func(xs ...interface{})()))(*(p)[i]) +OpPreCall, (const (println func(xs ...any)()))(*(p)[i]) OpCall, println OpCallNativeBody, println OpReturn, [FRAME FUNC:println RECV:(undefined) (1 args) 5/2/0/3/3 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] @@ -936,8 +936,8 @@ OpCompositeLit, [(const (1 int))](const-type int){(const (0 int))} OpEval, (const (0 int)) OpArrayLit, [(const (1 int))](const-type int){(const (0 int))} OpDefine, a := [(const (1 int))](const-type int){(const (0 int))} -OpExec, bodyStmt[0/0/1]=for i := (const (0 int)); (const-type bool)(i < (const (1 int))); i++ { (const (len func(x interface{})( int)))(a) -OpBody, for i := (const (0 int)); (const-type bool)(i < (const (1 int))); i++ { (const (len func(x interface{})( int)))(a) } +OpExec, bodyStmt[0/0/1]=for i := (const (0 int)); (const-type bool)(i < (const (1 int))); i++ { (const (len func(x any)( int)))(a) +OpBody, for i := (const (0 int)); (const-type bool)(i < (const (1 int))); i++ { (const (len func(x any)( int)))(a) } OpExec, i := (const (0 int)) OpEval, (const (0 int)) OpDefine, i := (const (0 int)) @@ -950,11 +950,11 @@ OpLss, (0 int) < (1 int) is true OpPreCall, (const-type bool)(i < (const (1 int))) OpConvert, Value: (true bool) | Type: bool OpExec, bodyStmt[0/0/-2]=(init) -OpForLoop, (const (len func(x interface{})( int)))(a) -OpEval, (const (len func(x interface{})( int)))(a) -OpEval, (const (len func(x interface{})( int))) +OpForLoop, (const (len func(x any)( int)))(a) +OpEval, (const (len func(x any)( int)))(a) +OpEval, (const (len func(x any)( int))) OpEval, a -OpPreCall, (const (len func(x interface{})( int)))(a) +OpPreCall, (const (len func(x any)( int)))(a) OpCall, len OpCallNativeBody, len OpReturn, [FRAME FUNC:len RECV:(undefined) (1 args) 5/1/0/3/3 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] @@ -1026,7 +1026,7 @@ func OpTypes() { b map[string]string c chan string d func() - e interface{} + e any }{} } diff --git a/gnovm/pkg/doc/doc.go b/gnovm/pkg/doc/doc.go index 989188a946a..71339ebf929 100644 --- a/gnovm/pkg/doc/doc.go +++ b/gnovm/pkg/doc/doc.go @@ -36,21 +36,14 @@ type WriteDocumentationOptions struct { } // Documentable is a package, symbol, or accessible which can be documented. -type Documentable interface { - WriteDocumentation(w io.Writer, opts *WriteDocumentationOptions) error -} - -// static implementation check -var _ Documentable = (*documentable)(nil) - -type documentable struct { +type Documentable struct { bfsDir symbol string accessible string pkgData *pkgData } -func (d *documentable) WriteDocumentation(w io.Writer, o *WriteDocumentationOptions) error { +func (d *Documentable) WriteDocumentation(w io.Writer, o *WriteDocumentationOptions) error { if o == nil { o = &WriteDocumentationOptions{} } @@ -110,7 +103,7 @@ func (d *documentable) WriteDocumentation(w io.Writer, o *WriteDocumentationOpti return d.output(pp) } -func (d *documentable) output(pp *pkgPrinter) (err error) { +func (d *Documentable) output(pp *pkgPrinter) (err error) { defer func() { // handle the case of errFatal. // this will have been generated by pkg.Fatalf, so get the error @@ -163,7 +156,7 @@ var fpAbs = filepath.Abs // dirs specifies the gno system directories to scan which specify full import paths // in their directories, such as @/examples and @/gnovm/stdlibs; modDirs specifies // directories which contain a gno.mod file. -func ResolveDocumentable(dirs, modDirs, args []string, unexported bool) (Documentable, error) { +func ResolveDocumentable(dirs, modDirs, args []string, unexported bool) (*Documentable, error) { d := newDirs(dirs, modDirs) parsed, ok := parseArgs(args) @@ -173,7 +166,7 @@ func ResolveDocumentable(dirs, modDirs, args []string, unexported bool) (Documen return resolveDocumentable(d, parsed, unexported) } -func resolveDocumentable(dirs *bfsDirs, parsed docArgs, unexported bool) (Documentable, error) { +func resolveDocumentable(dirs *bfsDirs, parsed docArgs, unexported bool) (*Documentable, error) { var candidates []bfsDir // if we have a candidate package name, search dirs for a dir that matches it. @@ -208,13 +201,13 @@ func resolveDocumentable(dirs *bfsDirs, parsed docArgs, unexported bool) (Docume } // we wanted documentation about a package, and we found one! if parsed.sym == "" { - return &documentable{bfsDir: candidates[0]}, nil + return &Documentable{bfsDir: candidates[0]}, nil } // we also have a symbol, and maybe accessible. // search for the symbol through the candidates - doc := &documentable{ + doc := &Documentable{ symbol: parsed.sym, accessible: parsed.acc, } @@ -246,7 +239,7 @@ func resolveDocumentable(dirs *bfsDirs, parsed docArgs, unexported bool) (Docume } doc.bfsDir = candidate doc.pkgData = pd - // match found. return this as documentable. + // match found. return this as Documentable. return doc, multierr.Combine(errs...) } } diff --git a/gnovm/pkg/doc/doc_test.go b/gnovm/pkg/doc/doc_test.go index 8a959e142d0..9883da54191 100644 --- a/gnovm/pkg/doc/doc_test.go +++ b/gnovm/pkg/doc/doc_test.go @@ -27,70 +27,70 @@ func TestResolveDocumentable(t *testing.T) { name string args []string unexp bool - expect Documentable + expect *Documentable errContains string }{ - {"package", []string{"crypto/rand"}, false, &documentable{bfsDir: getDir("crypto/rand")}, ""}, - {"packageMod", []string{"gno.land/mod"}, false, &documentable{bfsDir: getDir("mod")}, ""}, - {"dir", []string{"./testdata/integ/crypto/rand"}, false, &documentable{bfsDir: getDir("crypto/rand")}, ""}, - {"dirMod", []string{"./testdata/integ/mod"}, false, &documentable{bfsDir: getDir("mod")}, ""}, - {"dirAbs", []string{path("crypto/rand")}, false, &documentable{bfsDir: getDir("crypto/rand")}, ""}, + {"package", []string{"crypto/rand"}, false, &Documentable{bfsDir: getDir("crypto/rand")}, ""}, + {"packageMod", []string{"gno.land/mod"}, false, &Documentable{bfsDir: getDir("mod")}, ""}, + {"dir", []string{"./testdata/integ/crypto/rand"}, false, &Documentable{bfsDir: getDir("crypto/rand")}, ""}, + {"dirMod", []string{"./testdata/integ/mod"}, false, &Documentable{bfsDir: getDir("mod")}, ""}, + {"dirAbs", []string{path("crypto/rand")}, false, &Documentable{bfsDir: getDir("crypto/rand")}, ""}, // test_notapkg exists in local dir and also path("test_notapkg"). // ResolveDocumentable should first try local dir, and seeing as it is not a valid dir, try searching it as a package. - {"dirLocalMisleading", []string{"test_notapkg"}, false, &documentable{bfsDir: getDir("test_notapkg")}, ""}, + {"dirLocalMisleading", []string{"test_notapkg"}, false, &Documentable{bfsDir: getDir("test_notapkg")}, ""}, { "normalSymbol", []string{"crypto/rand.Flag"}, false, - &documentable{bfsDir: getDir("crypto/rand"), symbol: "Flag", pkgData: pdata("crypto/rand", false)}, "", + &Documentable{bfsDir: getDir("crypto/rand"), symbol: "Flag", pkgData: pdata("crypto/rand", false)}, "", }, { "normalAccessible", []string{"crypto/rand.Generate"}, false, - &documentable{bfsDir: getDir("crypto/rand"), symbol: "Generate", pkgData: pdata("crypto/rand", false)}, "", + &Documentable{bfsDir: getDir("crypto/rand"), symbol: "Generate", pkgData: pdata("crypto/rand", false)}, "", }, { "normalSymbolUnexp", []string{"crypto/rand.unexp"}, true, - &documentable{bfsDir: getDir("crypto/rand"), symbol: "unexp", pkgData: pdata("crypto/rand", true)}, "", + &Documentable{bfsDir: getDir("crypto/rand"), symbol: "unexp", pkgData: pdata("crypto/rand", true)}, "", }, { "normalAccessibleFull", []string{"crypto/rand.Rand.Name"}, false, - &documentable{bfsDir: getDir("crypto/rand"), symbol: "Rand", accessible: "Name", pkgData: pdata("crypto/rand", false)}, "", + &Documentable{bfsDir: getDir("crypto/rand"), symbol: "Rand", accessible: "Name", pkgData: pdata("crypto/rand", false)}, "", }, { "disambiguate", []string{"rand.Flag"}, false, - &documentable{bfsDir: getDir("crypto/rand"), symbol: "Flag", pkgData: pdata("crypto/rand", false)}, "", + &Documentable{bfsDir: getDir("crypto/rand"), symbol: "Flag", pkgData: pdata("crypto/rand", false)}, "", }, { "disambiguate2", []string{"rand.Crypto"}, false, - &documentable{bfsDir: getDir("crypto/rand"), symbol: "Crypto", pkgData: pdata("crypto/rand", false)}, "", + &Documentable{bfsDir: getDir("crypto/rand"), symbol: "Crypto", pkgData: pdata("crypto/rand", false)}, "", }, { "disambiguate3", []string{"rand.Normal"}, false, - &documentable{bfsDir: getDir("rand"), symbol: "Normal", pkgData: pdata("rand", false)}, "", + &Documentable{bfsDir: getDir("rand"), symbol: "Normal", pkgData: pdata("rand", false)}, "", }, { "disambiguate4", // just "rand" should use the directory that matches it exactly. []string{"rand"}, false, - &documentable{bfsDir: getDir("rand")}, "", + &Documentable{bfsDir: getDir("rand")}, "", }, { "wdSymbol", []string{"WdConst"}, false, - &documentable{bfsDir: getDir("wd"), symbol: "WdConst", pkgData: pdata("wd", false)}, "", + &Documentable{bfsDir: getDir("wd"), symbol: "WdConst", pkgData: pdata("wd", false)}, "", }, {"errInvalidArgs", []string{"1", "2", "3"}, false, nil, "invalid arguments: [1 2 3]"}, @@ -120,7 +120,7 @@ func TestResolveDocumentable(t *testing.T) { ) // we use stripFset because d.pkgData.fset contains sync/atomic values, // which in turn makes reflect.DeepEqual compare the two sync.Atomic values. - assert.Equal(t, stripFset(tc.expect), stripFset(result), "documentables should match") + assert.Equal(t, stripFset(tc.expect), stripFset(result), "Documentables should match") if tc.errContains == "" { assert.NoError(t, err) } else { @@ -130,11 +130,11 @@ func TestResolveDocumentable(t *testing.T) { } } -func stripFset(p Documentable) Documentable { - if d, ok := p.(*documentable); ok && d.pkgData != nil { +func stripFset(d *Documentable) *Documentable { + if d != nil && d.pkgData != nil { d.pkgData.fset = nil } - return p + return d } func TestDocument(t *testing.T) { @@ -150,30 +150,30 @@ func TestDocument(t *testing.T) { tt := []struct { name string - d *documentable + d *Documentable opts *WriteDocumentationOptions contains []string }{ - {"base", &documentable{bfsDir: dir}, nil, []string{"func Crypto", "!Crypto symbol", "func NewRand", "!unexp", "type Flag", "!Name"}}, - {"func", &documentable{bfsDir: dir, symbol: "crypto"}, nil, []string{"Crypto symbol", "func Crypto", "!func NewRand", "!type Flag"}}, - {"funcWriter", &documentable{bfsDir: dir, symbol: "NewWriter"}, nil, []string{"func NewWriter() io.Writer", "!func Crypto"}}, - {"tp", &documentable{bfsDir: dir, symbol: "Rand"}, nil, []string{"type Rand", "comment1", "!func Crypto", "!unexp ", "!comment4", "Has unexported"}}, - {"tpField", &documentable{bfsDir: dir, symbol: "Rand", accessible: "Value"}, nil, []string{"type Rand", "!comment1", "comment2", "!func Crypto", "!unexp", "elided"}}, + {"base", &Documentable{bfsDir: dir}, nil, []string{"func Crypto", "!Crypto symbol", "func NewRand", "!unexp", "type Flag", "!Name"}}, + {"func", &Documentable{bfsDir: dir, symbol: "crypto"}, nil, []string{"Crypto symbol", "func Crypto", "!func NewRand", "!type Flag"}}, + {"funcWriter", &Documentable{bfsDir: dir, symbol: "NewWriter"}, nil, []string{"func NewWriter() io.Writer", "!func Crypto"}}, + {"tp", &Documentable{bfsDir: dir, symbol: "Rand"}, nil, []string{"type Rand", "comment1", "!func Crypto", "!unexp ", "!comment4", "Has unexported"}}, + {"tpField", &Documentable{bfsDir: dir, symbol: "Rand", accessible: "Value"}, nil, []string{"type Rand", "!comment1", "comment2", "!func Crypto", "!unexp", "elided"}}, { "tpUnexp", - &documentable{bfsDir: dir, symbol: "Rand"}, + &Documentable{bfsDir: dir, symbol: "Rand"}, &WriteDocumentationOptions{Unexported: true}, []string{"type Rand", "comment1", "!func Crypto", "unexp ", "comment4", "!Has unexported"}, }, { "symUnexp", - &documentable{bfsDir: dir, symbol: "unexp"}, + &Documentable{bfsDir: dir, symbol: "unexp"}, &WriteDocumentationOptions{Unexported: true}, []string{"var unexp", "!type Rand", "!comment1", "!comment4", "!func Crypto", "!Has unexported"}, }, { "fieldUnexp", - &documentable{bfsDir: dir, symbol: "Rand", accessible: "unexp"}, + &Documentable{bfsDir: dir, symbol: "Rand", accessible: "unexp"}, &WriteDocumentationOptions{Unexported: true}, []string{"type Rand", "!comment1", "comment4", "!func Crypto", "elided", "!Has unexported"}, }, diff --git a/gnovm/pkg/doc/json_doc.go b/gnovm/pkg/doc/json_doc.go new file mode 100644 index 00000000000..cc7181e0fa6 --- /dev/null +++ b/gnovm/pkg/doc/json_doc.go @@ -0,0 +1,228 @@ +package doc + +import ( + "fmt" + "go/ast" + "go/doc" + "go/format" + "go/token" + "strings" + + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/tm2/pkg/amino" +) + +// JSONDocumentation holds package documentation suitable for transmitting +// as JSON with printable string fields +type JSONDocumentation struct { + PackagePath string `json:"package_path"` + PackageLine string `json:"package_line"` // package io // import "io" + PackageDoc string `json:"package_doc"` // markdown of top-level package documentation + // https://pkg.go.dev/go/doc#Package.Markdown to render markdown + + // These match each of the sections in a pkg.go.dev package documentation + Values []*JSONValueDecl `json:"values"` // constants and variables declared + Funcs []*JSONFunc `json:"funcs"` // Funcs and methods + Types []*JSONType `json:"types"` +} + +type JSONValueDecl struct { + Signature string `json:"signature"` + Const bool `json:"const"` + Values []*JSONValue `json:"values"` + Doc string `json:"doc"` // markdown +} + +type JSONValue struct { + Name string `json:"name"` + Doc string `json:"doc"` + Type string `json:"type"` // often empty +} + +type JSONField struct { + Name string + Type string +} + +type JSONFunc struct { + Type string `json:"type"` // if this is a method + Name string `json:"name"` + Signature string `json:"signature"` + Doc string `json:"doc"` // markdown + Params []*JSONField `json:"params"` + Results []*JSONField `json:"results"` +} + +type JSONType struct { + Name string `json:"name"` + Signature string `json:"signature"` + Doc string `json:"doc"` // markdown +} + +// NewDocumentableFromMemPkg gets the pkgData from memPkg and returns a Documentable +func NewDocumentableFromMemPkg(memPkg *gnovm.MemPackage, unexported bool, symbol, accessible string) (*Documentable, error) { + pd, err := newPkgDataFromMemPkg(memPkg, unexported) + if err != nil { + return nil, err + } + + doc := &Documentable{ + bfsDir: pd.dir, + pkgData: pd, + symbol: symbol, + accessible: accessible, + } + return doc, nil +} + +// WriteJSONDocumentation returns a JSONDocumentation for the package +func (d *Documentable) WriteJSONDocumentation() (*JSONDocumentation, error) { + opt := &WriteDocumentationOptions{} + _, pkg, err := d.pkgData.docPackage(opt) + if err != nil { + return nil, err + } + + jsonDoc := &JSONDocumentation{ + PackagePath: d.pkgData.dir.dir, + PackageLine: fmt.Sprintf("package %s // import %q", pkg.Name, pkg.ImportPath), + PackageDoc: string(pkg.Markdown(pkg.Doc)), + Values: []*JSONValueDecl{}, + Funcs: []*JSONFunc{}, + Types: []*JSONType{}, + } + + for _, value := range pkg.Consts { + jsonDoc.Values = append(jsonDoc.Values, &JSONValueDecl{ + Signature: mustFormatNode(d.pkgData.fset, value.Decl), + Const: true, + Values: d.extractValueSpecs(pkg, value.Decl.Specs), + Doc: string(pkg.Markdown(value.Doc)), + }) + } + + for _, value := range pkg.Vars { + jsonDoc.Values = append(jsonDoc.Values, &JSONValueDecl{ + Signature: mustFormatNode(d.pkgData.fset, value.Decl), + Const: false, + Values: d.extractValueSpecs(pkg, value.Decl.Specs), + Doc: string(pkg.Markdown(value.Doc)), + }) + } + + for _, fun := range pkg.Funcs { + jsonDoc.Funcs = append(jsonDoc.Funcs, &JSONFunc{ + Name: fun.Name, + Signature: mustFormatNode(d.pkgData.fset, fun.Decl), + Doc: string(pkg.Markdown(fun.Doc)), + Params: d.extractJSONFields(fun.Decl.Type.Params), + Results: d.extractJSONFields(fun.Decl.Type.Results), + }) + } + + for _, typ := range pkg.Types { + jsonDoc.Types = append(jsonDoc.Types, &JSONType{ + Name: typ.Name, + Signature: mustFormatNode(d.pkgData.fset, typ.Decl), + Doc: string(pkg.Markdown(typ.Doc)), + }) + + // constructors for this type + for _, fun := range typ.Funcs { + jsonDoc.Funcs = append(jsonDoc.Funcs, &JSONFunc{ + Name: fun.Name, + Signature: mustFormatNode(d.pkgData.fset, fun.Decl), + Doc: string(pkg.Markdown(fun.Doc)), + Params: d.extractJSONFields(fun.Decl.Type.Params), + Results: d.extractJSONFields(fun.Decl.Type.Results), + }) + } + + for _, meth := range typ.Methods { + jsonDoc.Funcs = append(jsonDoc.Funcs, &JSONFunc{ + Type: typ.Name, + Name: meth.Name, + Signature: mustFormatNode(d.pkgData.fset, meth.Decl), + Doc: string(pkg.Markdown(meth.Doc)), + Params: d.extractJSONFields(meth.Decl.Type.Params), + Results: d.extractJSONFields(meth.Decl.Type.Results), + }) + } + } + + return jsonDoc, nil +} + +func (d *Documentable) extractJSONFields(fieldList *ast.FieldList) []*JSONField { + results := []*JSONField{} + if fieldList != nil { + for _, field := range fieldList.List { + if len(field.Names) == 0 { + // if there are no names, then the field is unnamed, but still has a type + f := &JSONField{ + Name: "", + Type: mustFormatNode(d.pkgData.fset, field.Type), + } + results = append(results, f) + } else { + // fields can be of the format: (a, b int, c string) + // so we need to iterate over the names + for _, name := range field.Names { + f := &JSONField{ + Name: name.Name, + Type: mustFormatNode(d.pkgData.fset, field.Type), + } + results = append(results, f) + } + } + } + } + return results +} + +func (d *Documentable) extractValueSpecs(pkg *doc.Package, specs []ast.Spec) []*JSONValue { + values := []*JSONValue{} + + for _, value := range specs { + constSpec := value.(*ast.ValueSpec) + + typeString := "" + if constSpec.Type != nil { + typeString = mustFormatNode(d.pkgData.fset, constSpec.Type) + } + + commentBuf := new(strings.Builder) + if constSpec.Comment != nil { + for _, comment := range constSpec.Comment.List { + commentBuf.WriteString(comment.Text) + } + } + + // Const declaration can be of the form: const a, b, c = 1, 2, 3 + // so we need to iterate over the names + for _, name := range constSpec.Names { + jsonValue := &JSONValue{ + Name: name.Name, + Type: typeString, + Doc: string(pkg.Markdown(commentBuf.String())), + } + values = append(values, jsonValue) + } + } + return values +} + +// mustFormatNode calls format.Node and returns the result as a string. +// Panic on error, which shouldn't happen since the node is a valid AST from pkgData.parseFile. +func mustFormatNode(fset *token.FileSet, node any) string { + buf := new(strings.Builder) + if err := format.Node(buf, fset, node); err != nil { + panic("Error in format.Node: " + err.Error()) + } + return buf.String() +} + +func (jsonDoc *JSONDocumentation) JSON() string { + bz := amino.MustMarshalJSON(jsonDoc) + return string(bz) +} diff --git a/gnovm/pkg/doc/json_doc_test.go b/gnovm/pkg/doc/json_doc_test.go new file mode 100644 index 00000000000..59b1dc7eba5 --- /dev/null +++ b/gnovm/pkg/doc/json_doc_test.go @@ -0,0 +1,179 @@ +package doc + +import ( + "path/filepath" + "testing" + + "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestJSONDocumentation(t *testing.T) { + dir, err := filepath.Abs("./testdata/integ/hello") + require.NoError(t, err) + pkgPath := "gno.land/r/hello" + expected := &JSONDocumentation{ + PackagePath: "gno.land/r/hello", + PackageLine: "package hello // import \"hello\"", + PackageDoc: "hello is a package for testing\n", + Values: []*JSONValueDecl{ + { + Signature: "const ConstString = \"const string\"", + Const: true, + Doc: "", + Values: []*JSONValue{ + { + Name: "ConstString", + Doc: "", + Type: "", + }, + }, + }, + { + Signature: "var (\n\tpvString = \"private string\" // A private var\n\tPubString = \"public string\"\n)", + Const: false, + Doc: "Test public and private vars\n", + Values: []*JSONValue{ + { + Name: "pvString", + Doc: "// A private var\n", + Type: "", + }, + { + Name: "PubString", + Doc: "", + Type: "", + }, + }, + }, + { + Signature: "var counter int = 42", + Const: false, + Doc: "", + Values: []*JSONValue{ + { + Name: "counter", + Doc: "", + Type: "int", + }, + }, + }, + { + Signature: "var myStructInst = myStruct{a: 1000}", + Const: false, + Doc: "", + Values: []*JSONValue{ + { + Name: "myStructInst", + Doc: "", + Type: "", + }, + }, + }, + { + Signature: "var sl = []int{1, 2, 3, 4, 5}", + Const: false, + Doc: "sl is an int array\n", + Values: []*JSONValue{ + { + Name: "sl", + Doc: "", + Type: "", + }, + }, + }, + }, + Funcs: []*JSONFunc{ + { + Type: "", + Name: "Echo", + Signature: "func Echo(msg string) (res string)", + Doc: "", + Params: []*JSONField{ + {Name: "msg", Type: "string"}, + }, + Results: []*JSONField{ + {Name: "res", Type: "string"}, + }, + }, + { + Type: "", + Name: "GetCounter", + Signature: "func GetCounter() int", + Doc: "", + Params: []*JSONField{}, + Results: []*JSONField{ + {Name: "", Type: "int"}, + }, + }, + { + Type: "", + Name: "Inc", + Signature: "func Inc() int", + Doc: "", + Params: []*JSONField{}, + Results: []*JSONField{ + {Name: "", Type: "int"}, + }, + }, + { + Type: "", + Name: "Panic", + Signature: "func Panic()", + Doc: "Panic is a func for testing\n", + Params: []*JSONField{}, + Results: []*JSONField{}, + }, + { + Type: "", + Name: "fn", + Signature: "func fn() func(string) string", + Doc: "", + Params: []*JSONField{}, + Results: []*JSONField{ + {Name: "", Type: "func(string) string"}, + }, + }, + { + Type: "", + Name: "pvEcho", + Signature: "func pvEcho(msg string) string", + Doc: "", + Params: []*JSONField{ + {Name: "msg", Type: "string"}, + }, + Results: []*JSONField{ + {Name: "", Type: "string"}, + }, + }, + { + Type: "myStruct", + Name: "Foo", + Signature: "func (ms myStruct) Foo() string", + Doc: "Foo is a method for testing\n", + Params: []*JSONField{}, + Results: []*JSONField{ + {Name: "", Type: "string"}, + }, + }, + }, + Types: []*JSONType{ + { + Name: "myStruct", + Signature: "type myStruct struct{ a int }", + Doc: "myStruct is a struct for testing\n", + }, + }, + } + + // Get the JSONDocumentation similar to VMKeeper.QueryDoc + memPkg, err := gnolang.ReadMemPackage(dir, pkgPath) + require.NoError(t, err) + d, err := NewDocumentableFromMemPkg(memPkg, true, "", "") + require.NoError(t, err) + jdoc, err := d.WriteJSONDocumentation() + require.NoError(t, err) + + assert.Equal(t, expected.JSON(), jdoc.JSON()) +} diff --git a/gnovm/pkg/doc/pkg.go b/gnovm/pkg/doc/pkg.go index 71e1a50f299..f0ffa54e072 100644 --- a/gnovm/pkg/doc/pkg.go +++ b/gnovm/pkg/doc/pkg.go @@ -6,9 +6,11 @@ import ( "go/doc" "go/parser" "go/token" - "os" "path/filepath" "strings" + + "github.com/gnolang/gno/gnovm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" ) type pkgData struct { @@ -36,40 +38,46 @@ type symbolData struct { } func newPkgData(dir bfsDir, unexported bool) (*pkgData, error) { - files, err := os.ReadDir(dir.dir) + memPkg, err := gnolang.ReadMemPackage(dir.dir, dir.importPath) if err != nil { - return nil, fmt.Errorf("commands/doc: open %q: %w", dir.dir, err) + return nil, fmt.Errorf("commands/doc: read files %q: %w", dir.dir, err) } + return newPkgDataFromMemPkg(memPkg, unexported) +} + +func newPkgDataFromMemPkg(memPkg *gnovm.MemPackage, unexported bool) (*pkgData, error) { pkg := &pkgData{ - dir: dir, + dir: bfsDir{ + importPath: memPkg.Name, + dir: memPkg.Path, + }, fset: token.NewFileSet(), } - for _, file := range files { - n := file.Name() + for _, file := range memPkg.Files { + n := file.Name // Ignore files with prefix . or _ like go tools do. // Ignore _filetest.gno, but not _test.gno, as we use those to compute // examples. - if file.IsDir() || - !strings.HasSuffix(n, ".gno") || + if !strings.HasSuffix(n, ".gno") || strings.HasPrefix(n, ".") || strings.HasPrefix(n, "_") || strings.HasSuffix(n, "_filetest.gno") { continue } - fullPath := filepath.Join(dir.dir, n) - err := pkg.parseFile(fullPath, unexported) + err := pkg.parseFile(n, file.Body, unexported) if err != nil { + fullPath := filepath.Join(memPkg.Path, n) return nil, fmt.Errorf("commands/doc: parse file %q: %w", fullPath, err) } } if len(pkg.files) == 0 { - return nil, fmt.Errorf("commands/doc: no valid gno files in %q", dir.dir) + return nil, fmt.Errorf("commands/doc: no valid gno files in %q", memPkg.Path) } pkgName := pkg.files[0].Name.Name for _, file := range pkg.files[1:] { if file.Name.Name != pkgName { - return nil, fmt.Errorf("commands/doc: multiple packages (%q / %q) in dir %q", pkgName, file.Name.Name, dir.dir) + return nil, fmt.Errorf("commands/doc: multiple packages (%q / %q) in dir %q", pkgName, file.Name.Name, memPkg.Path) } } pkg.name = pkgName @@ -77,13 +85,8 @@ func newPkgData(dir bfsDir, unexported bool) (*pkgData, error) { return pkg, nil } -func (pkg *pkgData) parseFile(fileName string, unexported bool) error { - f, err := os.Open(fileName) - if err != nil { - return err - } - defer f.Close() - astf, err := parser.ParseFile(pkg.fset, filepath.Base(fileName), f, parser.ParseComments) +func (pkg *pkgData) parseFile(fileName string, body string, unexported bool) error { + astf, err := parser.ParseFile(pkg.fset, fileName, body, parser.ParseComments) if err != nil { return err } diff --git a/gnovm/pkg/doc/testdata/integ/hello/hello.gno b/gnovm/pkg/doc/testdata/integ/hello/hello.gno new file mode 100644 index 00000000000..d2035c2a951 --- /dev/null +++ b/gnovm/pkg/doc/testdata/integ/hello/hello.gno @@ -0,0 +1,33 @@ +// hello is a package for testing +package hello + +// sl is an int array +var sl = []int{1, 2, 3, 4, 5} + +func fn() func(string) string { return Echo } + +// myStruct is a struct for testing +type myStruct struct{ a int } + +var myStructInst = myStruct{a: 1000} + +// Foo is a method for testing +func (ms myStruct) Foo() string { return "myStruct.Foo" } + +// Panic is a func for testing +func Panic() { panic("foo") } + +var counter int = 42 + +// Test public and private vars +var ( + pvString = "private string" // A private var + PubString = "public string" +) + +const ConstString = "const string" + +func Echo(msg string) (res string) { res = "echo:" + msg; return } +func GetCounter() int { return counter } +func Inc() int { counter += 1; return counter } +func pvEcho(msg string) string { return "pvecho:" + msg } diff --git a/gnovm/pkg/gnolang/README.md b/gnovm/pkg/gnolang/README.md index 56ac6e0baba..2cd7c2b27d2 100644 --- a/gnovm/pkg/gnolang/README.md +++ b/gnovm/pkg/gnolang/README.md @@ -1,3 +1,4 @@ # Gnolang -TODO: dedicated README +## Declarations +* Gno is only available for 64-bit architectures! diff --git a/gnovm/pkg/gnolang/debug.go b/gnovm/pkg/gnolang/debug.go index c7f9311ffe4..0f9cb9a1f9c 100644 --- a/gnovm/pkg/gnolang/debug.go +++ b/gnovm/pkg/gnolang/debug.go @@ -3,6 +3,8 @@ package gnolang import ( "fmt" "net/http" + "path" + "runtime" "strings" "time" @@ -48,7 +50,10 @@ var enabled bool = true func (debugging) Println(args ...interface{}) { if debug { if enabled { - fmt.Println(append([]interface{}{"DEBUG:"}, args...)...) + _, file, line, _ := runtime.Caller(2) + caller := fmt.Sprintf("%-.12s:%-4d", path.Base(file), line) + prefix := fmt.Sprintf("DEBUG: %17s: ", caller) + fmt.Println(append([]interface{}{prefix}, args...)...) } } } @@ -56,7 +61,10 @@ func (debugging) Println(args ...interface{}) { func (debugging) Printf(format string, args ...interface{}) { if debug { if enabled { - fmt.Printf("DEBUG: "+format, args...) + _, file, line, _ := runtime.Caller(2) + caller := fmt.Sprintf("%.12s:%-4d", path.Base(file), line) + prefix := fmt.Sprintf("DEBUG: %17s: ", caller) + fmt.Printf(prefix+format, args...) } } } diff --git a/gnovm/pkg/gnolang/debugger.go b/gnovm/pkg/gnolang/debugger.go index f047a176af7..6b7b2f77175 100644 --- a/gnovm/pkg/gnolang/debugger.go +++ b/gnovm/pkg/gnolang/debugger.go @@ -10,11 +10,14 @@ import ( "io" "net" "os" + "path" "path/filepath" "sort" "strconv" "strings" "unicode" + + "github.com/gnolang/gno/gnovm/pkg/gnoenv" ) // DebugState is the state of the machine debugger, defined by a finite state @@ -43,24 +46,28 @@ type Debugger struct { out io.Writer // debugger output, defaults to Stdout scanner *bufio.Scanner // to parse input per line - state DebugState // current state of debugger - lastCmd string // last debugger command - lastArg string // last debugger command arguments - loc Location // source location of the current machine instruction - prevLoc Location // source location of the previous machine instruction - breakpoints []Location // list of breakpoints set by user, as source locations - call []Location // for function tracking, ideally should be provided by machine frame - frameLevel int // frame level of the current machine instruction - getSrc func(string) string // helper to access source from repl or others + state DebugState // current state of debugger + lastCmd string // last debugger command + lastArg string // last debugger command arguments + loc Location // source location of the current machine instruction + prevLoc Location // source location of the previous machine instruction + nextLoc Location // source location at the 'next' command + breakpoints []Location // list of breakpoints set by user, as source locations + call []Location // for function tracking, ideally should be provided by machine frame + frameLevel int // frame level of the current machine instruction + nextDepth int // function call depth at the 'next' command + getSrc func(string, string) string // helper to access source from repl or others + rootDir string } // Enable makes the debugger d active, using in as input reader, out as output writer and f as a source helper. -func (d *Debugger) Enable(in io.Reader, out io.Writer, f func(string) string) { +func (d *Debugger) Enable(in io.Reader, out io.Writer, f func(string, string) string) { d.in = in d.out = out d.enabled = true d.state = DebugAtInit d.getSrc = f + d.rootDir = gnoenv.RootDir() } // Disable makes the debugger d inactive. @@ -92,11 +99,11 @@ func init() { "list": {debugList, listUsage, listShort, listLong}, "print": {debugPrint, printUsage, printShort, ""}, "stack": {debugStack, stackUsage, stackShort, ""}, - // NOTE: the difference between continue, step and stepi is handled within - // the main Debug() loop. - "step": {debugContinue, stepUsage, stepShort, ""}, - "stepi": {debugContinue, stepiUsage, stepiShort, ""}, - "up": {debugUp, upUsage, upShort, ""}, + "next": {debugContinue, nextUsage, nextShort, ""}, + "step": {debugContinue, stepUsage, stepShort, ""}, + "stepi": {debugContinue, stepiUsage, stepiShort, ""}, + "stepout": {debugContinue, stepoutUsage, stepoutShort, ""}, + "up": {debugUp, upUsage, upShort, ""}, } // Sort command names for help. @@ -113,11 +120,13 @@ func init() { debugCmds["c"] = debugCmds["continue"] debugCmds["h"] = debugCmds["help"] debugCmds["l"] = debugCmds["list"] + debugCmds["n"] = debugCmds["next"] debugCmds["p"] = debugCmds["print"] debugCmds["quit"] = debugCmds["exit"] debugCmds["q"] = debugCmds["exit"] debugCmds["s"] = debugCmds["step"] debugCmds["si"] = debugCmds["stepi"] + debugCmds["so"] = debugCmds["stepout"] } // Debug is the debug callback invoked at each VM execution step. It implements the DebugState FSA. @@ -135,6 +144,9 @@ loop: fmt.Fprintln(m.Debugger.out, "Command failed:", err) } case DebugAtRun: + if !m.Debugger.enabled { + break loop + } switch m.Debugger.lastCmd { case "si", "stepi": m.Debugger.state = DebugAtCmd @@ -146,6 +158,21 @@ loop: debugList(m, "") continue loop } + case "n", "next": + if m.Debugger.loc != m.Debugger.prevLoc && m.Debugger.loc.File != "" && + (m.Debugger.nextDepth == 0 || !sameLine(m.Debugger.loc, m.Debugger.nextLoc) && callDepth(m) <= m.Debugger.nextDepth) { + m.Debugger.state = DebugAtCmd + m.Debugger.prevLoc = m.Debugger.loc + debugList(m, "") + continue loop + } + case "stepout", "so": + if callDepth(m) < m.Debugger.nextDepth { + m.Debugger.state = DebugAtCmd + m.Debugger.prevLoc = m.Debugger.loc + debugList(m, "") + continue loop + } default: if atBreak(m) { m.Debugger.state = DebugAtCmd @@ -172,6 +199,23 @@ loop: } } +// callDepth returns the function call depth. +func callDepth(m *Machine) int { + n := 0 + for _, f := range m.Frames { + if f.Func == nil { + continue + } + n++ + } + return n +} + +// sameLine returns true if both arguments are at the same line. +func sameLine(loc1, loc2 Location) bool { + return loc1.PkgPath == loc2.PkgPath && loc1.File == loc2.File && loc1.Line == loc2.Line +} + // atBreak returns true if current machine location matches a breakpoint, false otherwise. func atBreak(m *Machine) bool { loc := m.Debugger.loc @@ -317,7 +361,14 @@ func parseLocSpec(m *Machine, arg string) (loc Location, err error) { if loc.File, err = filepath.Abs(strs[0]); err != nil { return loc, err } - loc.File = filepath.Clean(loc.File) + loc.File = path.Clean(loc.File) + if m.Debugger.rootDir != "" && strings.HasPrefix(loc.File, m.Debugger.rootDir) { + loc.File = strings.TrimPrefix(loc.File, m.Debugger.rootDir+"/gnovm/stdlibs/") + loc.File = strings.TrimPrefix(loc.File, m.Debugger.rootDir+"/examples/") + loc.File = strings.TrimPrefix(loc.File, m.Debugger.rootDir+"/") + loc.PkgPath = path.Dir(loc.File) + loc.File = path.Base(loc.File) + } } if line, err = strconv.Atoi(strs[1]); err != nil { return loc, err @@ -378,24 +429,29 @@ func debugClear(m *Machine, arg string) error { } // --------------------------------------- +// NOTE: the difference between continue, next, step, stepi and stepout is handled within the Debug() loop. const ( continueUsage = `continue|c` continueShort = `Run until breakpoint or program termination.` -) -const ( + nextUsage = `next|n` + nextShort = `Step over to next source line.` + stepUsage = `step|s` stepShort = `Single step through program.` -) -const ( stepiUsage = `stepi|si` stepiShort = `Single step a single VM instruction.` + + stepoutUsage = `stepout|so` + stepoutShort = `Step out of the current function.` ) func debugContinue(m *Machine, arg string) error { m.Debugger.state = DebugAtRun m.Debugger.frameLevel = 0 + m.Debugger.nextDepth = callDepth(m) + m.Debugger.nextLoc = m.Debugger.loc return nil } @@ -505,7 +561,7 @@ func debugList(m *Machine, arg string) (err error) { if err != nil { // Use optional getSrc helper as fallback to get source. if m.Debugger.getSrc != nil { - src = m.Debugger.getSrc(loc.File) + src = m.Debugger.getSrc(loc.PkgPath, loc.File) } if src == "" { return err diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index 926ff0595e6..b8ed7b76c35 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -28,8 +28,7 @@ func evalTest(debugAddr, in, file string) (out, err string) { bout := bytes.NewBufferString("") berr := bytes.NewBufferString("") stdin := bytes.NewBufferString(in) - stdout := writeNopCloser{bout} - stderr := writeNopCloser{berr} + output := test.OutputWithError(writeNopCloser{bout}, writeNopCloser{berr}) defer func() { if r := recover(); r != nil { @@ -39,14 +38,14 @@ func evalTest(debugAddr, in, file string) (out, err string) { err = strings.TrimSpace(strings.ReplaceAll(err, "../../tests/files/", "files/")) }() - _, testStore := test.Store(gnoenv.RootDir(), false, stdin, stdout, stderr) + _, testStore := test.Store(gnoenv.RootDir(), output) f := gnolang.MustReadFile(file) m := gnolang.NewMachineWithOptions(gnolang.MachineOptions{ PkgPath: string(f.PkgName), Input: stdin, - Output: stdout, + Output: output, Store: testStore, Context: test.Context(string(f.PkgName), nil), Debug: true, @@ -143,6 +142,9 @@ func TestDebug(t *testing.T) { {in: "b 27\nc\np b\n", out: `("!zero" string)`}, {in: "b 22\nc\np t.A[3]\n", out: "Command failed: &{(\"slice index out of bounds: 3 (len=3)\" string) }"}, {in: "b 43\nc\nc\nc\np i\ndetach\n", out: "(1 int)"}, + {in: "b 37\nc\nnext\n", out: "=> 39:"}, + {in: "b 40\nc\nnext\n", out: "=> 41:"}, + {in: "b 22\nc\nstepout\n", out: "=> 40:"}, }) runDebugTest(t, "../../tests/files/a1.gno", []dtest{ diff --git a/gnovm/pkg/gnolang/doc.go b/gnovm/pkg/gnolang/doc.go index e52d54470e9..4fe9536d47f 100644 --- a/gnovm/pkg/gnolang/doc.go +++ b/gnovm/pkg/gnolang/doc.go @@ -1,2 +1,7 @@ // SPDX-License-Identifier: GNO License Version 1.0 + +// Package gnolang contains the implementation of the Gno Virtual Machine. package gnolang + +//go:generate -command stringer go run -modfile ../../../misc/devdeps/go.mod golang.org/x/tools/cmd/stringer +//go:generate stringer -type=Kind,Op,TransCtrl,TransField,VPType,Word -output string_methods.go . diff --git a/gnovm/pkg/gnolang/files_test.go b/gnovm/pkg/gnolang/files_test.go index 2c82f6d8f29..a05f088dd16 100644 --- a/gnovm/pkg/gnolang/files_test.go +++ b/gnovm/pkg/gnolang/files_test.go @@ -45,9 +45,9 @@ func TestFiles(t *testing.T) { Error: io.Discard, Sync: *withSync, } - o.BaseStore, o.TestStore = test.Store( - rootDir, true, - nopReader{}, o.WriterForStore(), io.Discard, + o.BaseStore, o.TestStore = test.StoreWithOptions( + rootDir, o.WriterForStore(), + test.StoreOptions{WithExtern: true}, ) return o } @@ -121,7 +121,7 @@ func TestStdlibs(t *testing.T) { capture = new(bytes.Buffer) out = capture } - opts = test.NewTestOptions(rootDir, nopReader{}, out, out) + opts = test.NewTestOptions(rootDir, out, out) opts.Verbose = true return } @@ -179,4 +179,41 @@ func TestStdlibs(t *testing.T) { if err != nil { t.Fatal(err) } + + testDir := "../../tests/stdlibs/" + testFs := os.DirFS(testDir) + err = fs.WalkDir(testFs, ".", func(path string, de fs.DirEntry, err error) error { + switch { + case err != nil: + return err + case !de.IsDir() || path == ".": + return nil + } + if _, err := os.Stat(filepath.Join(dir, path)); err == nil { + // skip; this dir exists already in the normal stdlibs and we + // currently don't support testing these "mixed stdlibs". + return nil + } + + fp := filepath.Join(testDir, path) + memPkg := gnolang.MustReadMemPackage(fp, path) + t.Run("test-"+strings.ReplaceAll(memPkg.Path, "/", "-"), func(t *testing.T) { + if sharedCapture != nil { + sharedCapture.Reset() + } + + err := test.Test(memPkg, "", sharedOpts) + if !testing.Verbose() { + t.Log(sharedCapture.String()) + } + if err != nil { + t.Error(err) + } + }) + + return nil + }) + if err != nil { + t.Fatal(err) + } } diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index 60f19979b7a..b1d47855093 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -102,12 +102,12 @@ type Defer struct { } type StacktraceCall struct { - Stmt Stmt Frame *Frame } type Stacktrace struct { Calls []StacktraceCall NumFramesElided int + LastLine int } func (s Stacktrace) String() string { @@ -117,6 +117,12 @@ func (s Stacktrace) String() string { if s.NumFramesElided > 0 && i == maxStacktraceSize/2 { fmt.Fprintf(&builder, "...%d frame(s) elided...\n", s.NumFramesElided) } + var line int + if i == 0 { + line = s.LastLine + } else { + line = s.Calls[i-1].Frame.Source.GetLine() + } call := s.Calls[i] cx := call.Frame.Source.(*CallExpr) @@ -126,7 +132,7 @@ func (s Stacktrace) String() string { fmt.Fprintf(&builder, " gonative:%s.%s\n", call.Frame.Func.NativePkg, call.Frame.Func.NativeName) case call.Frame.Func != nil: fmt.Fprintf(&builder, "%s\n", toExprTrace(cx)) - fmt.Fprintf(&builder, " %s/%s:%d\n", call.Frame.Func.PkgPath, call.Frame.Func.FileName, call.Stmt.GetLine()) + fmt.Fprintf(&builder, " %s/%s:%d\n", call.Frame.Func.PkgPath, call.Frame.Func.FileName, line) case call.Frame.GoFunc != nil: fmt.Fprintf(&builder, "%s\n", toExprTrace(cx)) fmt.Fprintf(&builder, " gofunction:%s\n", call.Frame.GoFunc.Value.Type()) @@ -216,3 +222,30 @@ func toConstExpTrace(cte *ConstExpr) string { return tv.T.String() } + +//---------------------------------------- +// Exception + +// Exception represents a panic that originates from a gno program. +type Exception struct { + // Value is the value passed to panic. + Value TypedValue + // Frame is used to reference the frame a panic occurred in so that recover() knows if the + // currently executing deferred function is able to recover from the panic. + Frame *Frame + + Stacktrace Stacktrace +} + +func (e Exception) Sprint(m *Machine) string { + return e.Value.Sprint(m) +} + +// UnhandledPanicError represents an error thrown when a panic is not handled in the realm. +type UnhandledPanicError struct { + Descriptor string // Description of the unhandled panic. +} + +func (e UnhandledPanicError) Error() string { + return e.Descriptor +} diff --git a/gnovm/pkg/gnolang/fuzz_test.go b/gnovm/pkg/gnolang/fuzz_test.go index 977c7453b90..f19c35a3da1 100644 --- a/gnovm/pkg/gnolang/fuzz_test.go +++ b/gnovm/pkg/gnolang/fuzz_test.go @@ -50,8 +50,8 @@ func FuzzConvertUntypedBigdecToFloat(f *testing.F) { func FuzzParseFile(f *testing.F) { // 1. Add the corpra. - parseFileDir := filepath.Join("testdata", "corpra", "parsefile") - paths, err := filepath.Glob(filepath.Join(parseFileDir, "*.go")) + parseFileDir := filepath.Join("testdata", "corpora", "parsefile") + paths, err := filepath.Glob(filepath.Join(parseFileDir, "*.go_fuzz")) if err != nil { f.Fatal(err) } diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index 5a8c6faf315..8ee9a0d3ca3 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -52,8 +52,6 @@ func TestBuiltinIdentifiersShadowing(t *testing.T) { "iota", "append", "cap", - "close", - "complex", "copy", "delete", "len", @@ -64,7 +62,6 @@ func TestBuiltinIdentifiersShadowing(t *testing.T) { "println", "recover", "nil", - "bigint", "bool", "byte", "float32", @@ -81,10 +78,10 @@ func TestBuiltinIdentifiersShadowing(t *testing.T) { "uint16", "uint32", "uint64", - "typeval", "error", "true", "false", + "any", } for _, name := range uverseNames { @@ -168,90 +165,254 @@ func TestConvertTo(t *testing.T) { tests := []cases{ { `package test - -func main() { - const a int = -1 - println(uint(a)) -}`, - `test/main.go:5:13: cannot convert constant of type IntKind to UintKind`, + func main() { + var t interface{} + t = 2 + var g = float32(t) + println(g) + } + `, `test/main.go:5:12: cannot convert interface{} to float32: need type assertion`, }, { `package test - -func main() { - const a int = -1 - println(uint8(a)) -}`, - `test/main.go:5:13: cannot convert constant of type IntKind to Uint8Kind`, + func main() { + var t interface{} + t = 2 + var g = int(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to int: need type assertion`, }, { `package test - -func main() { - const a int = -1 - println(uint16(a)) -}`, - `test/main.go:5:13: cannot convert constant of type IntKind to Uint16Kind`, + func main() { + var t interface{} + t = 2 + var g = int8(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to int8: need type assertion`, }, { `package test - -func main() { - const a int = -1 - println(uint32(a)) -}`, - `test/main.go:5:13: cannot convert constant of type IntKind to Uint32Kind`, + func main() { + var t interface{} + t = 2 + var g = int16(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to int16: need type assertion`, }, { `package test - -func main() { - const a int = -1 - println(uint64(a)) -}`, - `test/main.go:5:13: cannot convert constant of type IntKind to Uint64Kind`, + func main() { + var t interface{} + t = 2 + var g = int32(t) + println(g) + } + `, `test/main.go:5:16: cannot convert interface{} to int32: need type assertion`, }, { `package test - -func main() { - const a float32 = 1.5 - println(int32(a)) -}`, - `test/main.go:5:13: cannot convert constant of type Float32Kind to Int32Kind`, + func main() { + var t interface{} + t = 2 + var g = int64(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to int64: need type assertion`, }, { `package test - -func main() { - println(int32(1.5)) -}`, - `test/main.go:4:13: cannot convert (const (1.5 bigdec)) to integer type`, + func main() { + var t interface{} + t = 2 + var g = uint(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to uint: need type assertion`, + }, + { + `package test + func main() { + var t interface{} + t = 2 + var g = uint8(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to uint8: need type assertion`, }, { `package test + func main() { + var t interface{} + t = 2 + var g = uint16(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to uint16: need type assertion`, + }, + { + `package test + func main() { + var t interface{} + t = 2 + var g = uint32(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to uint32: need type assertion`, + }, + { + `package test + func main() { + var t interface{} + t = 2 + var g = uint64(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to uint64: need type assertion`, + }, -func main() { - const a float64 = 1.5 - println(int64(a)) -}`, - `test/main.go:5:13: cannot convert constant of type Float64Kind to Int64Kind`, + // Built-in non-numeric types + { + `package test + func main() { + var t interface{} + t = "hello" + var g = string(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to string: need type assertion`, }, { `package test + func main() { + var t interface{} + t = true + var g = bool(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to bool: need type assertion`, + }, + { + `package test + func main() { + var t interface{} + t = 'a' + var g = rune(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to int32: need type assertion`, + }, + { + `package test + func main() { + var t interface{} + t = byte(65) + var g = byte(t) + println(g) + } + `, `test/main.go:5:14: cannot convert interface{} to uint8: need type assertion`, + }, -func main() { - println(int64(1.5)) -}`, - `test/main.go:4:13: cannot convert (const (1.5 bigdec)) to integer type`, + { + `package test + type MyInt int + func main() { + var t interface{} + t = MyInt(2) + var g = MyInt(t) + println(g) + } + `, `test/main.go:6:14: cannot convert interface{} to test.MyInt: need type assertion`, + }, + { + `package test + + func main() { + const a int = -1 + println(uint(a)) + }`, + `test/main.go:5:14: cannot convert constant of type IntKind to UintKind`, + }, + { + `package test + + func main() { + const a int = -1 + println(uint8(a)) + }`, + `test/main.go:5:14: cannot convert constant of type IntKind to Uint8Kind`, + }, + { + `package test + + func main() { + const a int = -1 + println(uint16(a)) + }`, + `test/main.go:5:14: cannot convert constant of type IntKind to Uint16Kind`, }, { `package test func main() { - const f = float64(1.0) - println(int64(f)) + const a int = -1 + println(uint32(a)) }`, + `test/main.go:5:14: cannot convert constant of type IntKind to Uint32Kind`, + }, + { + `package test + + func main() { + const a int = -1 + println(uint64(a)) + }`, + `test/main.go:5:14: cannot convert constant of type IntKind to Uint64Kind`, + }, + { + `package test + + func main() { + const a float32 = 1.5 + println(int32(a)) + }`, + `test/main.go:5:14: cannot convert constant of type Float32Kind to Int32Kind`, + }, + { + `package test + + func main() { + println(int32(1.5)) + }`, + `test/main.go:4:14: cannot convert (const (1.5 bigdec)) to integer type`, + }, + { + `package test + + func main() { + const a float64 = 1.5 + println(int64(a)) + }`, + `test/main.go:5:14: cannot convert constant of type Float64Kind to Int64Kind`, + }, + { + `package test + + func main() { + println(int64(1.5)) + }`, + `test/main.go:4:14: cannot convert (const (1.5 bigdec)) to integer type`, + }, + { + `package test + + func main() { + const f = float64(1.0) + println(int64(f)) + }`, ``, }, } @@ -422,54 +583,6 @@ func assertOutput(t *testing.T, input string, output string) { assert.Nil(t, err) } -func TestRunMakeStruct(t *testing.T) { - t.Parallel() - - assertOutput(t, `package test -type Outfit struct { - Scarf string - Shirt string - Belts string - Strap string - Pants string - Socks string - Shoes string -} -func main() { - s := Outfit { - // some fields are out of order. - // some fields are left unset. - Scarf:"scarf", - Shirt:"shirt", - Shoes:"shoes", - Socks:"socks", - } - // some fields out of order are used. - // some fields left unset are used. - print(s.Shoes+","+s.Shirt+","+s.Pants+","+s.Scarf) -}`, `shoes,shirt,,scarf`) -} - -func TestRunReturnStruct(t *testing.T) { - t.Parallel() - - assertOutput(t, `package test -type MyStruct struct { - FieldA string - FieldB string -} -func myStruct(a, b string) MyStruct { - return MyStruct{ - FieldA: a, - FieldB: b, - } -} -func main() { - s := myStruct("aaa", "bbb") - print(s.FieldA+","+s.FieldB) -}`, `aaa,bbb`) -} - // ---------------------------------------- // Benchmarks diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 4c9de87a6a7..a50a727dd6b 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -149,6 +149,13 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } }() } + + panicWithPos := func(fmtStr string, args ...any) { + pos := fs.Position(gon.Pos()) + loc := fmt.Sprintf("%s:%d:%d", pos.Filename, pos.Line, pos.Column) + panic(fmt.Errorf("%s: %v", loc, fmt.Sprintf(fmtStr, args...))) + } + switch gon := gon.(type) { case *ast.ParenExpr: return toExpr(fs, gon.X) @@ -225,6 +232,8 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } case *ast.FuncLit: type_ := Go2Gno(fs, gon.Type).(*FuncTypeExpr) + type_.IsClosure = true + return &FuncLitExpr{ Type: *type_, Body: toBody(fs, gon.Body), @@ -243,10 +252,10 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { Tag: toExpr(fs, gon.Tag), } } else { - panic(fmt.Sprintf( + panicWithPos( "expected a Go Field with 1 name but got %v.\n"+ "maybe call toFields", - gon.Names)) + gon.Names) } case *ast.ArrayType: if _, ok := gon.Len.(*ast.Ellipsis); ok { @@ -328,7 +337,7 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { if cx, ok := gon.X.(*ast.CallExpr); ok { if ix, ok := cx.Fun.(*ast.Ident); ok && ix.Name == "panic" { if len(cx.Args) != 1 { - panic("expected panic statement to have single exception value") + panicWithPos("expected panic statement to have single exception value") } return &PanicStmt{ Exception: toExpr(fs, cx.Args[0]), @@ -410,9 +419,8 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { VarName: "", } default: - panic(fmt.Sprintf( - "unexpected *ast.TypeSwitchStmt.Assign type %s", - reflect.TypeOf(gon.Assign).String())) + panicWithPos("unexpected *ast.TypeSwitchStmt.Assign type %s", + reflect.TypeOf(gon.Assign).String()) } case *ast.SwitchStmt: x := toExpr(fs, gon.Tag) @@ -431,7 +439,10 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { recv := FieldTypeExpr{} if isMethod { if len(gon.Recv.List) > 1 { - panic("*ast.FuncDecl cannot have multiple receivers") + panicWithPos("method has multiple receivers") + } + if len(gon.Recv.List) == 0 { + panicWithPos("method has no receiver") } recv = *Go2Gno(fs, gon.Recv.List[0]).(*FieldTypeExpr) } @@ -449,7 +460,7 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { Body: body, } case *ast.GenDecl: - panic("unexpected *ast.GenDecl; use toDecls(fs,) instead") + panicWithPos("unexpected *ast.GenDecl; use toDecls(fs,) instead") case *ast.File: pkgName := Name(gon.Name.Name) decls := make([]Decl, 0, len(gon.Decls)) @@ -467,12 +478,21 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } case *ast.EmptyStmt: return &EmptyStmt{} + case *ast.IndexListExpr: + if len(gon.Indices) > 1 { + panicWithPos("invalid operation: more than one index") + } + panicWithPos("invalid operation: indexList is not permitted in Gno") + case *ast.GoStmt: + panicWithPos("goroutines are not permitted") default: - panic(fmt.Sprintf("unknown Go type %v: %s\n", + panicWithPos("unknown Go type %v: %s\n", reflect.TypeOf(gon), spew.Sdump(gon), - )) + ) } + + return } //---------------------------------------- @@ -651,12 +671,16 @@ func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { for _, id := range s.Names { names = append(names, *Nx(toName(id))) } - if s.Type == nil { + + // Inherit the last type when + // both type and value are nil + if s.Type == nil && s.Values == nil { tipe = lastType } else { tipe = toExpr(fs, s.Type) lastType = tipe } + if s.Values == nil { values = copyExprs(lastValues) } else { diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 85fc8b70051..532302cecf9 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -4,18 +4,31 @@ import ( "fmt" "math" "reflect" + "unsafe" "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) -// NOTE -// // GoNative, *NativeType, and *NativeValue are experimental and subject to -// change. +// change. These functions were originally created for bootstrapping Gno's +// (file) tests from the Yaegi project (which imported complex standard +// libraries). Then later they became used for native bindings in autogenerated +// code (see stdlibs/generated.go). +// +// Due to the limitations of Go reflection and due to the complexity of +// converting complex recursive data structures, neither Go2Gno* nor Gno2Go* +// will ever be complete for the general case. +// +// NOTE This means that new native bindings must conform to these limitations, +// namely use these for message-passing objects (read-only copies), rather than +// relying on much intelligence, such as those offered by go2GnoValueUpdate(). +// +// TODO list and document the limitations. // -// Go 1.15 reflect has a bug in creating new types with methods -- namely, it -// cannot, and so you cannot create types through reflection that obey any -// interface but the empty interface. +// * Go 1.15 reflect has a bug in creating new types with methods -- namely, +// it cannot, and so you cannot create types through reflection that obey any +// interface but the empty interface. +// * ... // ---------------------------------------- // Go to Gno conversion @@ -275,7 +288,8 @@ func Go2GnoNativeValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { return go2GnoValue(alloc, rv) } -// NOTE: used by imports_test.go TestSetOrigCaller. +// NOTE: used by imports_test.go TestSetOriginCaller. +// See also gno2GoValue(). func Gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { return gno2GoValue(tv, rv) } @@ -312,7 +326,7 @@ func go2GnoValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { case reflect.String: tv.V = alloc.NewString(rv.String()) case reflect.Int: - tv.SetInt(int(rv.Int())) + tv.SetInt(rv.Int()) case reflect.Int8: tv.SetInt8(int8(rv.Int())) case reflect.Int16: @@ -322,7 +336,7 @@ func go2GnoValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { case reflect.Int64: tv.SetInt64(rv.Int()) case reflect.Uint: - tv.SetUint(uint(rv.Uint())) + tv.SetUint(rv.Uint()) case reflect.Uint8: tv.SetUint8(uint8(rv.Uint())) case reflect.Uint16: @@ -366,6 +380,11 @@ func go2GnoValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { // become initialized. NOTE: Due to limitations of Go 1.15 // reflection, any child Gno declared types cannot change // types, and pointer values cannot change. +// +// NOTE This function is wildly experimental and for testing only. +// +// NOTE It doesn't even call m.DidUpdate(), so it can't be used for anything +// involving realm persistence. func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv reflect.Value) { // Special case if nil: if tv.IsUndefined() { @@ -391,7 +410,7 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv } case IntKind: if lvl != 0 { - tv.SetInt(int(rv.Int())) + tv.SetInt(rv.Int()) } case Int8Kind: if lvl != 0 { @@ -411,7 +430,7 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv } case UintKind: if lvl != 0 { - tv.SetUint(uint(rv.Uint())) + tv.SetUint(rv.Uint()) } case Uint8Kind: if lvl != 0 { @@ -488,12 +507,13 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv panic("go-native update error: slice length mismatch") } } - if sv.GetBase(nil).Data == nil { + svb := sv.GetBase(nil) + if svb.Data == nil { st := baseOf(tv.T).(*SliceType) et := st.Elt for i := 0; i < rvl; i++ { erv := rv.Index(i) - etv := &sv.GetBase(nil).List[svo+i] + etv := &svb.List[svo+i] // XXX use Assign and Realm? if etv.T == nil && et.Kind() != InterfaceKind { etv.T = et @@ -506,7 +526,7 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv } else { for i := 0; i < rvl; i++ { erv := rv.Index(i) - sv.GetBase(nil).Data[svo+i] = uint8(erv.Uint()) + svb.Data[svo+i] = uint8(erv.Uint()) } } case PointerKind: @@ -627,7 +647,7 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo case reflect.String: tv.V = alloc.NewString(rv.String()) case reflect.Int: - tv.SetInt(int(rv.Int())) + tv.SetInt(rv.Int()) case reflect.Int8: tv.SetInt8(int8(rv.Int())) case reflect.Int16: @@ -637,7 +657,7 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo case reflect.Int64: tv.SetInt64(rv.Int()) case reflect.Uint: - tv.SetUint(uint(rv.Uint())) + tv.SetUint(rv.Uint()) case reflect.Uint8: tv.SetUint8(uint8(rv.Uint())) case reflect.Uint16: @@ -769,9 +789,9 @@ func gno2GoType(t Type) reflect.Type { return reflect.TypeOf(float32(0)) case Float64Type: return reflect.TypeOf(float64(0)) - case BigintType, UntypedBigintType: + case UntypedBigintType: panic("not yet implemented") - case BigdecType, UntypedBigdecType: + case UntypedBigdecType: panic("not yet implemented") default: panic("should not happen") @@ -887,9 +907,9 @@ func gno2GoTypeMatches(t Type, rt reflect.Type) (result bool) { return rt.Kind() == reflect.Float32 case Float64Type: return rt.Kind() == reflect.Float64 - case BigintType, UntypedBigintType: + case UntypedBigintType: panic("not yet implemented") - case BigdecType, UntypedBigdecType: + case UntypedBigdecType: panic("not yet implemented") default: panic("should not happen") @@ -991,6 +1011,11 @@ func gno2GoTypeMatches(t Type, rt reflect.Type) (result bool) { // gno.PointerValue). In the latter case, an addressable one will be // constructed and returned, otherwise returns rv. if tv is undefined, rv must // be valid. +// +// NOTE It doesn't make sense to add a 'store' argument here to support lazy +// loading (e.g. from native function bindings for SDKParams) because it +// doesn't really make sense in the general case, and there is a FillValue() +// function available for eager fetching of ref values. func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { if tv.IsUndefined() { if debug { @@ -1032,7 +1057,7 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { case StringType, UntypedStringType: rv.SetString(tv.GetString()) case IntType: - rv.SetInt(int64(tv.GetInt())) + rv.SetInt(tv.GetInt()) case Int8Type: rv.SetInt(int64(tv.GetInt8())) case Int16Type: @@ -1042,7 +1067,7 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { case Int64Type: rv.SetInt(tv.GetInt64()) case UintType: - rv.SetUint(uint64(tv.GetUint())) + rv.SetUint(tv.GetUint()) case Uint8Type: rv.SetUint(uint64(tv.GetUint8())) case Uint16Type: @@ -1107,10 +1132,11 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { svo := sv.Offset svl := sv.Length svc := sv.Maxcap - if sv.GetBase(nil).Data == nil { + svb := sv.GetBase(nil) + if svb.Data == nil { rv.Set(reflect.MakeSlice(st, svl, svc)) for i := 0; i < svl; i++ { - etv := &(sv.GetBase(nil).List[svo+i]) + etv := &(svb.List[svo+i]) if etv.IsUndefined() { continue } @@ -1118,7 +1144,7 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { } } else { data := make([]byte, svl, svc) - copy(data[:svc], sv.GetBase(nil).Data[svo:svo+svc]) + copy(data[:svc], svb.Data[svo:svo+svc]) rv.Set(reflect.ValueOf(data)) } case *StructType: @@ -1133,7 +1159,12 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { if ftv.IsUndefined() { continue } - gno2GoValue(ftv, rv.Field(i)) + fv := rv.Field(i) + if !fv.CanSet() { + // Normally private fields can not bet set via reflect. Override this limitation. + fv = reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem() + } + gno2GoValue(ftv, fv) } case *MapType: // If uninitialized map, return zero value. @@ -1222,7 +1253,7 @@ func (m *Machine) doOpArrayLitGoNative() { if kx := x.Elts[i].Key; kx != nil { // XXX why convert? (also see doOpArrayLit()) k := kx.(*ConstExpr).ConvertGetInt() - rf := rv.Index(k) + rf := rv.Index(int(k)) // XXX: k may overflow on 32 bits plaforms. gno2GoValue(&itvs[i], rf) } else { rf := rv.Index(i) @@ -1261,7 +1292,7 @@ func (m *Machine) doOpSliceLitGoNative() { if kx := x.Elts[i].Key; kx != nil { // XXX why convert? (also see doOpArrayLit()) k := kx.(*ConstExpr).ConvertGetInt() - rf := rv.Index(k) + rf := rv.Index(int(k)) // XXX: k may overflow on 32 bits plaforms. gno2GoValue(&itvs[i], rf) } else { rf := rv.Index(i) diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go index ddc1fd2fa55..5f0064cc4d9 100644 --- a/gnovm/pkg/gnolang/helpers.go +++ b/gnovm/pkg/gnolang/helpers.go @@ -28,6 +28,22 @@ func IsRealmPath(pkgPath string) bool { !ReGnoRunPath.MatchString(pkgPath) } +// IsInternalPath determines whether the given pkgPath refers to an internal +// package, that may not be called directly or imported by packages that don't +// share the same root. +// +// If isInternal is true, base will be set to the root of the internal package, +// which must also be an ancestor or the same path that imports the given +// internal package. +func IsInternalPath(pkgPath string) (base string, isInternal bool) { + // Restrict imports to /internal packages to a package rooted at base. + var suff string + base, suff, isInternal = strings.Cut(pkgPath, "/internal") + // /internal should be either at the end, or be a part: /internal/ + isInternal = isInternal && (suff == "" || suff[0] == '/') + return +} + // IsPurePackagePath determines whether the given pkgpath is for a published Gno package. // It only considers "pure" those starting with gno.land/p/, so it returns false for // stdlib packages and MsgRun paths. diff --git a/gnovm/pkg/gnolang/internal/softfloat/copy.sh b/gnovm/pkg/gnolang/internal/softfloat/copy.sh deleted file mode 100644 index 6d2a8f80462..00000000000 --- a/gnovm/pkg/gnolang/internal/softfloat/copy.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - -# softfloat64.go: -# - add header -# - change package name -cat > runtime_softfloat64.go << EOF -// Code generated by copy.sh. DO NOT EDIT. -// This file is copied from \$GOROOT/src/runtime/softfloat64.go. -// It is the software floating point implementation used by the Go runtime. - -EOF -cat "$GOROOT/src/runtime/softfloat64.go" >> ./runtime_softfloat64.go -sed -i 's/^package runtime$/package softfloat/' runtime_softfloat64.go - -# softfloat64_test.go: -# - add header -# - change package name -# - change import to right package -# - change GOARCH to runtime.GOARCH, and import the "runtime" package -cat > runtime_softfloat64_test.go << EOF -// Code generated by copy.sh. DO NOT EDIT. -// This file is copied from \$GOROOT/src/runtime/softfloat64_test.go. -// It is the tests for the software floating point implementation -// used by the Go runtime. - -EOF -cat "$GOROOT/src/runtime/softfloat64_test.go" >> ./runtime_softfloat64_test.go -sed -i 's/^package runtime_test$/package softfloat_test/ -s#^\t\. "runtime"$#\t. "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"# -s/GOARCH/runtime.GOARCH/g -16a\ - "runtime"' runtime_softfloat64_test.go \ No newline at end of file diff --git a/gnovm/pkg/gnolang/internal/softfloat/gen/main.go b/gnovm/pkg/gnolang/internal/softfloat/gen/main.go new file mode 100644 index 00000000000..5895b53a93a --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/gen/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "errors" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +func main() { + // Process softfloat64.go file + processSoftFloat64File() + + // Process softfloat64_test.go file + processSoftFloat64TestFile() + + // Run mvdan.cc/gofumpt + gofumpt() + + fmt.Println("Files processed successfully.") +} + +func processSoftFloat64File() { + // Read source file + content, err := os.ReadFile(fmt.Sprintf("%s/src/runtime/softfloat64.go", runtime.GOROOT())) + if err != nil { + log.Fatal("Error reading source file:", err) + } + + // Prepare header + header := `// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT. +// This file is copied from $GOROOT/src/runtime/softfloat64.go. +// It is the software floating point implementation used by the Go runtime. + +` + + // Combine header with content + newContent := header + string(content) + + // Replace package name + newContent = strings.Replace(newContent, "package runtime", "package softfloat", 1) + + // Write to destination file + err = os.WriteFile("runtime_softfloat64.go", []byte(newContent), 0o644) + if err != nil { + log.Fatal("Error writing to destination file:", err) + } +} + +func processSoftFloat64TestFile() { + // Read source test file + content, err := os.ReadFile(fmt.Sprintf("%s/src/runtime/softfloat64_test.go", runtime.GOROOT())) + if err != nil { + log.Fatal("Error reading source test file:", err) + } + + // Prepare header + header := `// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT. +// This file is copied from $GOROOT/src/runtime/softfloat64_test.go. +// It is the tests for the software floating point implementation +// used by the Go runtime. + +` + + // Combine header with content + newContent := header + string(content) + + // Replace package name and imports + newContent = strings.Replace(newContent, "package runtime_test", "package softfloat_test", 1) + newContent = strings.Replace(newContent, "\t. \"runtime\"", "\t\"runtime\"", 1) + newContent = strings.Replace(newContent, "GOARCH", "runtime.GOARCH", 1) + + newContent = strings.Replace(newContent, "import (", "import (\n\t. \"github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat\"", 1) + + // Write to destination file + err = os.WriteFile("runtime_softfloat64_test.go", []byte(newContent), 0o644) + if err != nil { + log.Fatal("Error writing to destination test file:", err) + } +} + +func gitRoot() (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + p := wd + for { + if _, e := os.Stat(filepath.Join(p, ".git")); e == nil { + return p, nil + } + + if strings.HasSuffix(p, string(filepath.Separator)) { + return "", errors.New("root git not found") + } + + p = filepath.Dir(p) + } +} + +func gofumpt() { + rootPath, err := gitRoot() + if err != nil { + log.Fatal("error finding git root:", err) + } + + cmd := exec.Command("go", "run", "-modfile", filepath.Join(strings.TrimSpace(rootPath), "misc/devdeps/go.mod"), "mvdan.cc/gofumpt", "-w", ".") + _, err = cmd.Output() + if err != nil { + log.Fatal("error gofumpt:", err) + } +} diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go index cf2ad5afd8a..7623b9c2077 100644 --- a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go @@ -1,4 +1,4 @@ -// Code generated by copy.sh. DO NOT EDIT. +// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT. // This file is copied from $GOROOT/src/runtime/softfloat64.go. // It is the software floating point implementation used by the Go runtime. diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go index c57fe08b0ef..70c76655a97 100644 --- a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go @@ -1,4 +1,4 @@ -// Code generated by copy.sh. DO NOT EDIT. +// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT. // This file is copied from $GOROOT/src/runtime/softfloat64_test.go. // It is the tests for the software floating point implementation // used by the Go runtime. @@ -12,9 +12,10 @@ package softfloat_test import ( "math" "math/rand" - . "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" + "runtime" "testing" - "runtime" + + . "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) // turn uint64 op into float64 op diff --git a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go index 30f66dff620..89dcd04d8fb 100644 --- a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go +++ b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go @@ -17,7 +17,7 @@ package softfloat // This file mostly exports the functions from runtime_softfloat64.go -//go:generate sh copy.sh +//go:generate go run github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen const ( mask = 0x7FF diff --git a/gnovm/pkg/gnolang/internal/txlog/txlog.go b/gnovm/pkg/gnolang/internal/txlog/txlog.go index cda11083672..0c5e8640bf7 100644 --- a/gnovm/pkg/gnolang/internal/txlog/txlog.go +++ b/gnovm/pkg/gnolang/internal/txlog/txlog.go @@ -7,12 +7,14 @@ // when calling [MapCommitter.Commit]. package txlog +import "iter" + // Map is a generic interface to a key/value map, like Go's builtin map. type Map[K comparable, V any] interface { Get(K) (V, bool) Set(K, V) Delete(K) - Iterate() func(yield func(K, V) bool) + Iterate() iter.Seq2[K, V] } // MapCommitter is a Map which also implements a Commit() method, which writes @@ -47,7 +49,7 @@ func (m GoMap[K, V]) Delete(k K) { } // Iterate implements [Map]. -func (m GoMap[K, V]) Iterate() func(yield func(K, V) bool) { +func (m GoMap[K, V]) Iterate() iter.Seq2[K, V] { return func(yield func(K, V) bool) { for k, v := range m { if !yield(k, v) { @@ -103,21 +105,26 @@ func (b txLog[K, V]) Delete(k K) { b.dirty[k] = deletable[V]{deleted: true} } -func (b txLog[K, V]) Iterate() func(yield func(K, V) bool) { +func (b txLog[K, V]) Iterate() iter.Seq2[K, V] { return func(yield func(K, V) bool) { // go through b.source; skip deleted values, and use updated values // for those which exist in b.dirty. - b.source.Iterate()(func(k K, v V) bool { + for k, v := range b.source.Iterate() { if dirty, ok := b.dirty[k]; ok { if dirty.deleted { - return true + continue + } + if !yield(k, dirty.v) { + return } - return yield(k, dirty.v) + continue } // not in dirty - return yield(k, v) - }) + if !yield(k, v) { + return + } + } // iterate over all "new" values (ie. exist in b.dirty but not b.source). for k, v := range b.dirty { @@ -129,7 +136,7 @@ func (b txLog[K, V]) Iterate() func(yield func(K, V) bool) { continue } if !yield(k, v.v) { - break + return } } } diff --git a/gnovm/pkg/gnolang/internal/txlog/txlog_test.go b/gnovm/pkg/gnolang/internal/txlog/txlog_test.go index b0780fc8380..fd2a2597194 100644 --- a/gnovm/pkg/gnolang/internal/txlog/txlog_test.go +++ b/gnovm/pkg/gnolang/internal/txlog/txlog_test.go @@ -51,17 +51,17 @@ func ExampleWrap() { func Test_txLog(t *testing.T) { t.Parallel() - type Value = struct{} + type Value = struct{ b byte } // Full "integration test" of the txLog + mapwrapper. source := GoMap[int, *Value](map[int]*Value{}) // create 4 empty values (we'll just use the pointers) vs := [...]*Value{ - {}, - {}, - {}, - {}, + {0}, + {1}, + {2}, + {3}, } source.Set(0, vs[0]) source.Set(1, vs[1]) @@ -121,8 +121,8 @@ func Test_txLog(t *testing.T) { assert.Equal(t, saved, txm.source) // double-check on the iterators. - verifyHashMapValues(t, source, map[int]*Value{1: vs[1], 2: vs[0]}) - verifyHashMapValues(t, txm, map[int]*Value{2: vs[2], 3: vs[3]}) + verifyHashMapValues(t, source, map[int]*Value{1: vs[1], 2: vs[2]}) + verifyHashMapValues(t, txm, map[int]*Value{2: vs[0], 3: vs[3]}) } { @@ -146,17 +146,16 @@ func Test_txLog(t *testing.T) { } } -func verifyHashMapValues(t *testing.T, m Map[int, *struct{}], expectedReadonly map[int]*struct{}) { +func verifyHashMapValues(t *testing.T, m Map[int, *struct{ b byte }], expectedReadonly map[int]*struct{ b byte }) { t.Helper() expected := maps.Clone(expectedReadonly) - m.Iterate()(func(k int, v *struct{}) bool { + for k, v := range m.Iterate() { ev, eok := expected[k] _ = assert.True(t, eok, "mapping %d:%v should exist in expected map", k, v) && assert.Equal(t, ev, v, "values should match") delete(expected, k) - return true - }) + } assert.Empty(t, expected, "(some) expected values not found in the Map") } diff --git a/gnovm/pkg/gnolang/kind_string.go b/gnovm/pkg/gnolang/kind_string.go deleted file mode 100644 index 12e95829b20..00000000000 --- a/gnovm/pkg/gnolang/kind_string.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by "stringer -type=Kind ./pkg/gnolang"; DO NOT EDIT. - -package gnolang - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[InvalidKind-0] - _ = x[BoolKind-1] - _ = x[StringKind-2] - _ = x[IntKind-3] - _ = x[Int8Kind-4] - _ = x[Int16Kind-5] - _ = x[Int32Kind-6] - _ = x[Int64Kind-7] - _ = x[UintKind-8] - _ = x[Uint8Kind-9] - _ = x[Uint16Kind-10] - _ = x[Uint32Kind-11] - _ = x[Uint64Kind-12] - _ = x[Float32Kind-13] - _ = x[Float64Kind-14] - _ = x[BigintKind-15] - _ = x[BigdecKind-16] - _ = x[ArrayKind-17] - _ = x[SliceKind-18] - _ = x[PointerKind-19] - _ = x[StructKind-20] - _ = x[PackageKind-21] - _ = x[InterfaceKind-22] - _ = x[ChanKind-23] - _ = x[FuncKind-24] - _ = x[MapKind-25] - _ = x[TypeKind-26] - _ = x[BlockKind-27] - _ = x[HeapItemKind-28] - _ = x[TupleKind-29] - _ = x[RefTypeKind-30] -} - -const _Kind_name = "InvalidKindBoolKindStringKindIntKindInt8KindInt16KindInt32KindInt64KindUintKindUint8KindUint16KindUint32KindUint64KindFloat32KindFloat64KindBigintKindBigdecKindArrayKindSliceKindPointerKindStructKindPackageKindInterfaceKindChanKindFuncKindMapKindTypeKindBlockKindHeapItemKindTupleKindRefTypeKind" - -var _Kind_index = [...]uint16{0, 11, 19, 29, 36, 44, 53, 62, 71, 79, 88, 98, 108, 118, 129, 140, 150, 160, 169, 178, 189, 199, 210, 223, 231, 239, 246, 254, 263, 275, 284, 295} - -func (i Kind) String() string { - if i >= Kind(len(_Kind_index)-1) { - return "Kind(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _Kind_name[_Kind_index[i]:_Kind_index[i+1]] -} diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 75d12ac5402..4de316902fb 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -1,11 +1,11 @@ package gnolang -// XXX rename file to machine.go. - import ( "fmt" "io" + "path" "reflect" + "runtime" "slices" "strconv" "strings" @@ -18,30 +18,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/store" ) -// Exception represents a panic that originates from a gno program. -type Exception struct { - // Value is the value passed to panic. - Value TypedValue - // Frame is used to reference the frame a panic occurred in so that recover() knows if the - // currently executing deferred function is able to recover from the panic. - Frame *Frame - - Stacktrace Stacktrace -} - -func (e Exception) Sprint(m *Machine) string { - return e.Value.Sprint(m) -} - -// UnhandledPanicError represents an error thrown when a panic is not handled in the realm. -type UnhandledPanicError struct { - Descriptor string // Description of the unhandled panic. -} - -func (e UnhandledPanicError) Error() string { - return e.Descriptor -} - //---------------------------------------- // Machine @@ -66,8 +42,6 @@ type Machine struct { // Configuration PreprocessorMode bool // this is used as a flag when const values are evaluated during preprocessing - ReadOnly bool - MaxCycles int64 Output io.Writer Store Store Context interface{} @@ -103,7 +77,6 @@ type MachineOptions struct { // Active package of the given machine; must be set before execution. PkgPath string PreprocessorMode bool - ReadOnly bool Debug bool Input io.Reader // used for default debugger input only Output io.Writer // default os.Stdout @@ -111,7 +84,6 @@ type MachineOptions struct { Context interface{} Alloc *Allocator // or see MaxAllocBytes. MaxAllocBytes int64 // or 0 for no limit. - MaxCycles int64 // or 0 for no limit. GasMeter store.GasMeter } @@ -135,8 +107,6 @@ var machinePool = sync.Pool{ // [Machine.Release]. func NewMachineWithOptions(opts MachineOptions) *Machine { preprocessorMode := opts.PreprocessorMode - readOnly := opts.ReadOnly - maxCycles := opts.MaxCycles vmGasMeter := opts.GasMeter output := opts.Output @@ -168,8 +138,6 @@ func NewMachineWithOptions(opts MachineOptions) *Machine { mm.Package = pv mm.Alloc = alloc mm.PreprocessorMode = preprocessorMode - mm.ReadOnly = readOnly - mm.MaxCycles = maxCycles mm.Output = output mm.Store = store mm.Context = context @@ -283,11 +251,6 @@ func (m *Machine) RunMemPackageWithOverrides(memPkg *gnovm.MemPackage, save bool func (m *Machine) runMemPackage(memPkg *gnovm.MemPackage, save, overrides bool) (*PackageNode, *PackageValue) { // parse files. files := ParseMemPackage(memPkg) - if !overrides { - if err := checkDuplicates(files); err != nil { - panic(fmt.Errorf("running package %q: %w", memPkg.Path, err)) - } - } // make and set package if doesn't exist. pn := (*PackageNode)(nil) pv := (*PackageValue)(nil) @@ -303,7 +266,7 @@ func (m *Machine) runMemPackage(memPkg *gnovm.MemPackage, save, overrides bool) } m.SetActivePackage(pv) // run files. - updates := m.runFileDecls(files.Files...) + updates := m.runFileDecls(overrides, files.Files...) // save package value and mempackage. // XXX save condition will be removed once gonative is removed. var throwaway *Realm @@ -407,24 +370,17 @@ func destar(x Expr) Expr { // Stacktrace returns the stack trace of the machine. // It collects the executions and frames from the machine's frames and statements. func (m *Machine) Stacktrace() (stacktrace Stacktrace) { - if len(m.Frames) == 0 || len(m.Stmts) == 0 { + if len(m.Frames) == 0 { return } - calls := make([]StacktraceCall, 0, len(m.Stmts)) - nextStmtIndex := len(m.Stmts) - 1 + calls := make([]StacktraceCall, 0, len(m.Frames)) for i := len(m.Frames) - 1; i >= 0; i-- { if m.Frames[i].IsCall() { - stm := m.Stmts[nextStmtIndex] - bs := stm.(*bodyStmt) - stm = bs.Body[bs.NextBodyIndex-1] calls = append(calls, StacktraceCall{ - Stmt: stm, Frame: m.Frames[i], }) } - // if the frame is a call, the next statement is the last statement of the frame. - nextStmtIndex = m.Frames[i].NumStmts - 1 } // if the stacktrace is too long, we trim it down to maxStacktraceSize @@ -438,6 +394,19 @@ func (m *Machine) Stacktrace() (stacktrace Stacktrace) { stacktrace.Calls = calls + // XXX move to machine.LastLine()? + if len(m.Exprs) > 0 { + stacktrace.LastLine = m.PeekExpr(1).GetLine() + } else if len(m.Stmts) > 0 { + stmt := m.PeekStmt(1) + if bs, ok := stmt.(*bodyStmt); ok { + if 0 <= bs.NextBodyIndex-1 { + stmt = bs.Body[bs.NextBodyIndex-1] + } + } + stacktrace.LastLine = stmt.GetLine() + } + return } @@ -450,14 +419,59 @@ func (m *Machine) RunFiles(fns ...*FileNode) { if pv == nil { panic("RunFiles requires Machine.Package") } - updates := m.runFileDecls(fns...) + updates := m.runFileDecls(IsStdlib(pv.PkgPath), fns...) m.runInitFromUpdates(pv, updates) } +// PreprocessFiles runs Preprocess on the given files. It is used to detect +// compile-time errors in the package. +func (m *Machine) PreprocessFiles(pkgName, pkgPath string, fset *FileSet, save, withOverrides bool) (*PackageNode, *PackageValue) { + if !withOverrides { + if err := checkDuplicates(fset); err != nil { + panic(fmt.Errorf("running package %q: %w", pkgName, err)) + } + } + pn := NewPackageNode(Name(pkgName), pkgPath, fset) + pv := pn.NewPackage() + pb := pv.GetBlock(m.Store) + m.SetActivePackage(pv) + m.Store.SetBlockNode(pn) + PredefineFileSet(m.Store, pn, fset) + for _, fn := range fset.Files { + fn = Preprocess(m.Store, pn, fn).(*FileNode) + // After preprocessing, save blocknodes to store. + SaveBlockNodes(m.Store, fn) + // Make block for fn. + // Each file for each *PackageValue gets its own file *Block, + // with values copied over from each file's + // *FileNode.StaticBlock. + fb := m.Alloc.NewBlock(fn, pb) + fb.Values = make([]TypedValue, len(fn.StaticBlock.Values)) + copy(fb.Values, fn.StaticBlock.Values) + pv.AddFileBlock(fn.Name, fb) + } + // Get new values across all files in package. + pn.PrepareNewValues(pv) + // save package value. + var throwaway *Realm + if save { + // store new package values and types + throwaway = m.saveNewPackageValuesAndTypes() + if throwaway != nil { + m.Realm = throwaway + } + m.resavePackageValues(throwaway) + if throwaway != nil { + m.Realm = nil + } + } + return pn, pv +} + // Add files to the package's *FileSet and run decls in them. // This will also run each init function encountered. // Returns the updated typed values of package. -func (m *Machine) runFileDecls(fns ...*FileNode) []TypedValue { +func (m *Machine) runFileDecls(withOverrides bool, fns ...*FileNode) []TypedValue { // Files' package names must match the machine's active one. // if there is one. for _, fn := range fns { @@ -486,6 +500,11 @@ func (m *Machine) runFileDecls(fns ...*FileNode) []TypedValue { // add fns to pre-existing fileset. pn.FileSet.AddFiles(fns...) } + if !withOverrides { + if err := checkDuplicates(pn.FileSet); err != nil { + panic(fmt.Errorf("running package %q: %w", pv.PkgPath, err)) + } + } // Predefine declarations across all files. PredefineFileSet(m.Store, pn, fs) @@ -621,13 +640,13 @@ func (m *Machine) saveNewPackageValuesAndTypes() (throwaway *Realm) { if pv.IsRealm() { rlm := pv.Realm rlm.MarkNewReal(pv) - rlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + rlm.FinalizeRealmTransaction(m.Store) // save package realm info. m.Store.SetPackageRealm(rlm) } else { // use a throwaway realm. rlm := NewRealm(pv.PkgPath) rlm.MarkNewReal(pv) - rlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + rlm.FinalizeRealmTransaction(m.Store) throwaway = rlm } // save declared types. @@ -651,11 +670,11 @@ func (m *Machine) resavePackageValues(rlm *Realm) { pv := m.Package if pv.IsRealm() { rlm = pv.Realm - rlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + rlm.FinalizeRealmTransaction(m.Store) // re-save package realm info. m.Store.SetPackageRealm(rlm) } else { // use the throwaway realm. - rlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + rlm.FinalizeRealmTransaction(m.Store) } // types were already saved, and should not change // even after running the init function. @@ -666,10 +685,10 @@ func (m *Machine) RunFunc(fn Name) { if r := recover(); r != nil { switch r := r.(type) { case UnhandledPanicError: - fmt.Printf("Machine.RunFunc(%q) panic: %s\nStacktrace: %s\n", + fmt.Printf("Machine.RunFunc(%q) panic: %s\nStacktrace:\n%s\n", fn, r.Error(), m.ExceptionsStacktrace()) default: - fmt.Printf("Machine.RunFunc(%q) panic: %v\nMachine State:%s\nStacktrace: %s\n", + fmt.Printf("Machine.RunFunc(%q) panic: %v\nMachine State:%s\nStacktrace:\n%s\n", fn, r, m.String(), m.Stacktrace().String()) } panic(r) @@ -685,10 +704,10 @@ func (m *Machine) RunMain() { if r != nil { switch r := r.(type) { case UnhandledPanicError: - fmt.Printf("Machine.RunMain() panic: %s\nStacktrace: %s\n", + fmt.Printf("Machine.RunMain() panic: %s\nStacktrace:\n%s\n", r.Error(), m.ExceptionsStacktrace()) default: - fmt.Printf("Machine.RunMain() panic: %v\nMachine State:%s\nStacktrace: %s\n", + fmt.Printf("Machine.RunMain() panic: %v\nMachine State:%s\nStacktrace:\n%s\n", r, m.String(), m.Stacktrace()) } panic(r) @@ -1004,13 +1023,9 @@ const GasFactorCPU int64 = 1 func (m *Machine) incrCPU(cycles int64) { if m.GasMeter != nil { gasCPU := overflow.Mul64p(cycles, GasFactorCPU) - m.GasMeter.ConsumeGas(gasCPU, "CPUCycles") + m.GasMeter.ConsumeGas(gasCPU, "CPUCycles") // May panic if out of gas. } - m.Cycles += cycles - if m.MaxCycles != 0 && m.Cycles > m.MaxCycles { - panic("CPU cycle overrun") - } } const ( @@ -2095,14 +2110,51 @@ func (m *Machine) Panic(ex TypedValue) { m.PushOp(OpReturnCallDefers) } +// Recover is the underlying implementation of the recover() function in the +// GnoVM. It returns nil if there was no exception to be recovered, otherwise +// it returns the [Exception], which also contains the value passed into panic(). +func (m *Machine) Recover() *Exception { + if len(m.Exceptions) == 0 { + return nil + } + + // If the exception is out of scope, this recover can't help; return nil. + if m.PanicScope <= m.DeferPanicScope { + return nil + } + + exception := &m.Exceptions[len(m.Exceptions)-1] + + // If the frame the exception occurred in is not popped, it's possible that + // the exception is still in scope and can be recovered. + if !exception.Frame.Popped { + // If the frame is not the current frame, the exception is not in scope; return nil. + // This retrieves the second most recent call frame because the first most recent + // is the call to recover itself. + if frame := m.LastCallFrame(2); frame == nil || frame != exception.Frame { + return nil + } + } + + if isUntyped(exception.Value.T) { + ConvertUntypedTo(&exception.Value, nil) + } + // Recover complete; remove exceptions. + m.Exceptions = nil + return exception +} + //---------------------------------------- // inspection methods func (m *Machine) Println(args ...interface{}) { if debug { if enabled { - s := strings.Repeat("|", m.NumOps) - debug.Println(append([]interface{}{s}, args...)...) + _, file, line, _ := runtime.Caller(2) // get caller info + caller := fmt.Sprintf("%-.12s:%-4d", path.Base(file), line) + prefix := fmt.Sprintf("DEBUG: %17s: ", caller) + s := prefix + strings.Repeat("|", m.NumOps) + fmt.Println(append([]interface{}{s}, args...)...) } } } @@ -2110,8 +2162,11 @@ func (m *Machine) Println(args ...interface{}) { func (m *Machine) Printf(format string, args ...interface{}) { if debug { if enabled { - s := strings.Repeat("|", m.NumOps) - debug.Printf(s+" "+format, args...) + _, file, line, _ := runtime.Caller(2) // get caller info + caller := fmt.Sprintf("%-.12s:%-4d", path.Base(file), line) + prefix := fmt.Sprintf("DEBUG: %17s: ", caller) + s := prefix + strings.Repeat("|", m.NumOps) + fmt.Printf(s+" "+format, args...) } } } diff --git a/gnovm/pkg/gnolang/nocompile_on_32bits.go b/gnovm/pkg/gnolang/nocompile_on_32bits.go new file mode 100644 index 00000000000..03cb0855876 --- /dev/null +++ b/gnovm/pkg/gnolang/nocompile_on_32bits.go @@ -0,0 +1,10 @@ +package gnolang + +import "strconv" + +func _() { + // Restricting Gno to compile only on 64-bit architectures. + // Please see https://github.com/gnolang/gno/issues/3288 + var x [1]struct{} + _ = x[strconv.IntSize-64] +} diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index b85d1ac7026..a4938049c42 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -157,6 +157,7 @@ const ( ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used. ATTR_SHIFT_RHS GnoAttribute = "ATTR_SHIFT_RHS" ATTR_LAST_BLOCK_STMT GnoAttribute = "ATTR_LAST_BLOCK_STMT" + ATTR_GLOBAL GnoAttribute = "ATTR_GLOBAL" ) type Attributes struct { @@ -351,28 +352,11 @@ var ( type Expr interface { Node - assertExpr() + addressability() addressabilityStatus } type Exprs []Expr -// non-pointer receiver to help make immutable. -func (*NameExpr) assertExpr() {} -func (*BasicLitExpr) assertExpr() {} -func (*BinaryExpr) assertExpr() {} -func (*CallExpr) assertExpr() {} -func (*IndexExpr) assertExpr() {} -func (*SelectorExpr) assertExpr() {} -func (*SliceExpr) assertExpr() {} -func (*StarExpr) assertExpr() {} -func (*RefExpr) assertExpr() {} -func (*TypeAssertExpr) assertExpr() {} -func (*UnaryExpr) assertExpr() {} -func (*CompositeLitExpr) assertExpr() {} -func (*KeyValueExpr) assertExpr() {} -func (*FuncLitExpr) assertExpr() {} -func (*ConstExpr) assertExpr() {} - var ( _ Expr = &NameExpr{} _ Expr = &BasicLitExpr{} @@ -409,6 +393,10 @@ type NameExpr struct { Type NameExprType } +func (x *NameExpr) addressability() addressabilityStatus { + return addressabilityStatusSatisfied +} + type NameExprs []NameExpr type BasicLitExpr struct { @@ -420,6 +408,10 @@ type BasicLitExpr struct { Value string } +func (x *BasicLitExpr) addressability() addressabilityStatus { + return addressabilityStatusUnsatisfied +} + type BinaryExpr struct { // (Left Op Right) Attributes Left Expr // left operand @@ -427,26 +419,54 @@ type BinaryExpr struct { // (Left Op Right) Right Expr // right operand } +func (x *BinaryExpr) addressability() addressabilityStatus { + return addressabilityStatusUnsatisfied +} + type CallExpr struct { // Func(Args) Attributes - Func Expr // function expression - Args Exprs // function arguments, if any. - Varg bool // if true, final arg is variadic. - NumArgs int // len(Args) or len(Args[0].Results) + Func Expr // function expression + Args Exprs // function arguments, if any. + Varg bool // if true, final arg is variadic. + NumArgs int // len(Args) or len(Args[0].Results) + Addressability addressabilityStatus +} + +func (x *CallExpr) addressability() addressabilityStatus { + return x.Addressability } type IndexExpr struct { // X[Index] Attributes - X Expr // expression - Index Expr // index expression - HasOK bool // if true, is form: `value, ok := []` + X Expr // expression + Index Expr // index expression + HasOK bool // if true, is form: `value, ok := [] + Addressability addressabilityStatus +} + +func (x *IndexExpr) addressability() addressabilityStatus { + // If not set in TRANS_LEAVE, defer to the the child expression's addressability. + if x.Addressability == addressabilityStatusNotApplicable { + return x.X.addressability() + } + + return x.Addressability } type SelectorExpr struct { // X.Sel Attributes - X Expr // expression - Path ValuePath // set by preprocessor. - Sel Name // field selector + X Expr // expression + Path ValuePath // set by preprocessor. + Sel Name // field selector + IsAddressable bool // true if X is a pointer +} + +func (x *SelectorExpr) addressability() addressabilityStatus { + if x.IsAddressable || x.X.addressability() == addressabilityStatusSatisfied { + return addressabilityStatusSatisfied + } + + return addressabilityStatusUnsatisfied } type SliceExpr struct { // X[Low:High:Max] @@ -457,6 +477,10 @@ type SliceExpr struct { // X[Low:High:Max] Max Expr // maximum capacity of slice; or nil; added in Go 1.2 } +func (x *SliceExpr) addressability() addressabilityStatus { + return addressabilityStatusUnsatisfied +} + // A StarExpr node represents an expression of the form // "*" Expression. Semantically it could be a unary "*" // expression, or a pointer type. @@ -465,16 +489,33 @@ type StarExpr struct { // *X X Expr // operand } +func (x *StarExpr) addressability() addressabilityStatus { + return addressabilityStatusSatisfied +} + type RefExpr struct { // &X Attributes X Expr // operand } +func (x *RefExpr) addressability() addressabilityStatus { + return x.X.addressability() +} + type TypeAssertExpr struct { // X.(Type) Attributes - X Expr // expression. - Type Expr // asserted type, never nil. - HasOK bool // if true, is form: `_, ok := .()`. + X Expr // expression. + Type Expr // asserted type, never nil. + HasOK bool // if true, is form: `_, ok := .()`. + IsAddressable bool +} + +func (x *TypeAssertExpr) addressability() addressabilityStatus { + if x.IsAddressable { + return addressabilityStatusSatisfied + } + + return addressabilityStatusUnsatisfied } // A UnaryExpr node represents a unary expression. Unary @@ -487,12 +528,25 @@ type UnaryExpr struct { // (Op X) Op Word // operator } +func (x *UnaryExpr) addressability() addressabilityStatus { + return x.X.addressability() +} + // MyType{:} struct, array, slice, and map // expressions. type CompositeLitExpr struct { Attributes - Type Expr // literal type; or nil - Elts KeyValueExprs // list of struct fields; if any + Type Expr // literal type; or nil + Elts KeyValueExprs // list of struct fields; if any + IsAddressable bool +} + +func (x *CompositeLitExpr) addressability() addressabilityStatus { + if x.IsAddressable { + return addressabilityStatusSatisfied + } + + return addressabilityStatusUnsatisfied } // Returns true if any elements are keyed. @@ -525,6 +579,10 @@ type KeyValueExpr struct { Value Expr // never nil } +func (x *KeyValueExpr) addressability() addressabilityStatus { + return addressabilityStatusNotApplicable +} + type KeyValueExprs []KeyValueExpr // A FuncLitExpr node represents a function literal. Here one @@ -538,6 +596,10 @@ type FuncLitExpr struct { HeapCaptures NameExprs // filled in findLoopUses1 } +func (x *FuncLitExpr) addressability() addressabilityStatus { + return addressabilityStatusUnsatisfied +} + // The preprocessor replaces const expressions // with *ConstExpr nodes. type ConstExpr struct { @@ -546,6 +608,10 @@ type ConstExpr struct { TypedValue } +func (x *ConstExpr) addressability() addressabilityStatus { + return addressabilityStatusUnsatisfied +} + // ---------------------------------------- // Type(Expressions) // @@ -564,6 +630,8 @@ type TypeExpr interface { assertTypeExpr() } +const typeExprAddressability = "the addressability method should not be called on Type Expressions" + // non-pointer receiver to help make immutable. func (x *FieldTypeExpr) assertTypeExpr() {} func (x *ArrayTypeExpr) assertTypeExpr() {} @@ -576,17 +644,6 @@ func (x *StructTypeExpr) assertTypeExpr() {} func (x *constTypeExpr) assertTypeExpr() {} func (x *MaybeNativeTypeExpr) assertTypeExpr() {} -func (x *FieldTypeExpr) assertExpr() {} -func (x *ArrayTypeExpr) assertExpr() {} -func (x *SliceTypeExpr) assertExpr() {} -func (x *InterfaceTypeExpr) assertExpr() {} -func (x *ChanTypeExpr) assertExpr() {} -func (x *FuncTypeExpr) assertExpr() {} -func (x *MapTypeExpr) assertExpr() {} -func (x *StructTypeExpr) assertExpr() {} -func (x *constTypeExpr) assertExpr() {} -func (x *MaybeNativeTypeExpr) assertExpr() {} - var ( _ TypeExpr = &FieldTypeExpr{} _ TypeExpr = &ArrayTypeExpr{} @@ -610,6 +667,10 @@ type FieldTypeExpr struct { Tag Expr } +func (x *FieldTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type FieldTypeExprs []FieldTypeExpr func (ftxz FieldTypeExprs) IsNamed() bool { @@ -638,18 +699,30 @@ type ArrayTypeExpr struct { Elt Expr // element type } +func (x *ArrayTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type SliceTypeExpr struct { Attributes Elt Expr // element type Vrd bool // variadic arg expression } +func (x *SliceTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type InterfaceTypeExpr struct { Attributes Methods FieldTypeExprs // list of methods Generic Name // for uverse generics } +func (x *InterfaceTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type ChanDir int const ( @@ -667,10 +740,19 @@ type ChanTypeExpr struct { Value Expr // value type } +func (x *ChanTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type FuncTypeExpr struct { Attributes - Params FieldTypeExprs // (incoming) parameters, if any. - Results FieldTypeExprs // (outgoing) results, if any. + Params FieldTypeExprs // (incoming) parameters, if any. + Results FieldTypeExprs // (outgoing) results, if any. + IsClosure bool +} + +func (x *FuncTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) } type MapTypeExpr struct { @@ -679,11 +761,19 @@ type MapTypeExpr struct { Value Expr // value type } +func (x *MapTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + type StructTypeExpr struct { Attributes Fields FieldTypeExprs // list of field declarations } +func (x *StructTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + // Like ConstExpr but for types. type constTypeExpr struct { Attributes @@ -691,12 +781,20 @@ type constTypeExpr struct { Type Type } +func (x *constTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + // Only used for native func arguments type MaybeNativeTypeExpr struct { Attributes Type Expr } +func (x *MaybeNativeTypeExpr) addressability() addressabilityStatus { + panic(typeExprAddressability) +} + // ---------------------------------------- // Stmt // @@ -1234,12 +1332,13 @@ func ReadMemPackageFromList(list []string, pkgPath string) (*gnovm.MemPackage, e }) } + memPkg.Name = string(pkgName) + // If no .gno files are present, package simply does not exist. if !memPkg.IsEmpty() { if err := validatePkgName(string(pkgName)); err != nil { return nil, err } - memPkg.Name = string(pkgName) } return memPkg, nil @@ -1530,6 +1629,7 @@ type BlockNode interface { GetPathForName(Store, Name) ValuePath GetBlockNodeForPath(Store, ValuePath) BlockNode GetIsConst(Store, Name) bool + GetIsConstAt(Store, ValuePath) bool GetLocalIndex(Name) (uint16, bool) GetValueRef(Store, Name, bool) *TypedValue GetStaticTypeOf(Store, Name) Type @@ -1547,12 +1647,13 @@ type BlockNode interface { // Embed in node to make it a BlockNode. type StaticBlock struct { Block - Types []Type - NumNames uint16 - Names []Name - Consts []Name // TODO consider merging with Names. - Externs []Name - Loc Location + Types []Type + NumNames uint16 + Names []Name + UnassignableNames []Name + Consts []Name // TODO consider merging with Names. + Externs []Name + Loc Location // temporary storage for rolling back redefinitions. oldValues []oldValue @@ -1737,6 +1838,10 @@ func (sb *StaticBlock) GetIsConst(store Store, n Name) bool { } } +func (sb *StaticBlock) GetIsConstAt(store Store, path ValuePath) bool { + return sb.GetBlockNodeForPath(store, path).GetStaticBlock().getLocalIsConst(path.Name) +} + // Returns true iff n is a local const defined name. func (sb *StaticBlock) getLocalIsConst(n Name) bool { for _, name := range sb.Consts { @@ -1747,6 +1852,32 @@ func (sb *StaticBlock) getLocalIsConst(n Name) bool { return false } +func (sb *StaticBlock) IsAssignable(store Store, n Name) bool { + _, ok := sb.GetLocalIndex(n) + bp := sb.GetParentNode(store) + un := sb.UnassignableNames + + for { + if ok { + for _, uname := range un { + if n == uname { + return false + } + } + + return true + } else if bp != nil { + _, ok = bp.GetLocalIndex(n) + un = bp.GetStaticBlock().UnassignableNames + bp = bp.GetParentNode(store) + } else if _, ok := UverseNode().GetLocalIndex(n); ok { + return false + } else { + return true + } + } +} + // Implements BlockNode. // XXX XXX what about uverse? func (sb *StaticBlock) GetStaticTypeOf(store Store, n Name) Type { @@ -1878,7 +2009,7 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { n)) } old := sb.Block.Values[idx] - if !old.IsUndefined() { + if !old.IsUndefined() && tv.T != nil { if tv.T.Kind() == FuncKind && tv.T.(*FuncType).IsZero() { // special case, // allow re-predefining for func upgrades. @@ -1988,12 +2119,28 @@ func (x *PackageNode) SetBody(b Body) { // such as those for *DeclaredType methods or *StructType fields, // see tests/selector_test.go. type ValuePath struct { - Type VPType // see VPType* consts. + Type VPType // see VPType* consts. + // Warning: Use SetDepth() to set Depth. Depth uint8 // see doc for ValuePath. Index uint16 // index of value, field, or method. Name Name // name of value, field, or method. } +// Maximum depth of a ValuePath. +const MaxValuePathDepth = 127 + +func (vp ValuePath) validateDepth() { + if vp.Depth > MaxValuePathDepth { + panic(fmt.Sprintf("exceeded maximum %s depth (%d)", vp.Type, MaxValuePathDepth)) + } +} + +func (vp *ValuePath) SetDepth(d uint8) { + vp.Depth = d + + vp.validateDepth() +} + type VPType uint8 const ( @@ -2072,6 +2219,8 @@ func NewValuePathNative(n Name) ValuePath { } func (vp ValuePath) Validate() { + vp.validateDepth() + switch vp.Type { case VPUverse: if vp.Depth != 0 { @@ -2186,3 +2335,11 @@ func isHiddenResultVariable(name string) bool { } return false } + +type addressabilityStatus int + +const ( + addressabilityStatusNotApplicable addressabilityStatus = iota + addressabilityStatusSatisfied + addressabilityStatusUnsatisfied +) diff --git a/gnovm/pkg/gnolang/op_assign.go b/gnovm/pkg/gnolang/op_assign.go index 5e841fb18fd..c101058321b 100644 --- a/gnovm/pkg/gnolang/op_assign.go +++ b/gnovm/pkg/gnolang/op_assign.go @@ -12,13 +12,8 @@ func (m *Machine) doOpDefine() { nx := s.Lhs[i].(*NameExpr) // Finally, define (or assign if loop block). ptr := lb.GetPointerToMaybeHeapDefine(m.Store, nx) - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := ptr.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } + if !m.PreprocessorMode && isUntyped(rvs[i].T) && rvs[i].T.Kind() != BoolKind { + panic("untyped conversion should not happen at runtime") } ptr.Assign2(m.Alloc, m.Store, m.Realm, rvs[i], true) } @@ -33,13 +28,8 @@ func (m *Machine) doOpAssign() { for i := len(s.Lhs) - 1; 0 <= i; i-- { // Pop lhs value and desired type. lv := m.PopAsPointer(s.Lhs[i]) - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } + if !m.PreprocessorMode && isUntyped(rvs[i].T) && rvs[i].T.Kind() != BoolKind { + panic("untyped conversion should not happen at runtime") } lv.Assign2(m.Alloc, m.Store, m.Realm, rvs[i], true) } @@ -53,14 +43,6 @@ func (m *Machine) doOpAddAssign() { debugAssertSameTypes(lv.TV.T, rv.T) } - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // add rv to lv. addAssign(m.Alloc, lv.TV, rv) if lv.Base != nil { @@ -76,14 +58,6 @@ func (m *Machine) doOpSubAssign() { debugAssertSameTypes(lv.TV.T, rv.T) } - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // sub rv from lv. subAssign(lv.TV, rv) if lv.Base != nil { @@ -99,14 +73,6 @@ func (m *Machine) doOpMulAssign() { debugAssertSameTypes(lv.TV.T, rv.T) } - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // lv *= rv mulAssign(lv.TV, rv) if lv.Base != nil { @@ -122,14 +88,6 @@ func (m *Machine) doOpQuoAssign() { debugAssertSameTypes(lv.TV.T, rv.T) } - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // lv /= rv err := quoAssign(lv.TV, rv) if err != nil { @@ -149,14 +107,6 @@ func (m *Machine) doOpRemAssign() { debugAssertSameTypes(lv.TV.T, rv.T) } - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // lv %= rv err := remAssign(lv.TV, rv) if err != nil { @@ -176,14 +126,6 @@ func (m *Machine) doOpBandAssign() { debugAssertSameTypes(lv.TV.T, rv.T) } - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // lv &= rv bandAssign(lv.TV, rv) if lv.Base != nil { @@ -199,14 +141,6 @@ func (m *Machine) doOpBandnAssign() { debugAssertSameTypes(lv.TV.T, rv.T) } - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // lv &^= rv bandnAssign(lv.TV, rv) if lv.Base != nil { @@ -222,14 +156,6 @@ func (m *Machine) doOpBorAssign() { debugAssertSameTypes(lv.TV.T, rv.T) } - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // lv |= rv borAssign(lv.TV, rv) if lv.Base != nil { @@ -245,14 +171,6 @@ func (m *Machine) doOpXorAssign() { debugAssertSameTypes(lv.TV.T, rv.T) } - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // lv ^= rv xorAssign(lv.TV, rv) if lv.Base != nil { @@ -265,14 +183,6 @@ func (m *Machine) doOpShlAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // lv <<= rv shlAssign(m, lv.TV, rv) if lv.Base != nil { @@ -285,14 +195,6 @@ func (m *Machine) doOpShrAssign() { rv := m.PopValue() // only one. lv := m.PopAsPointer(s.Lhs[0]) - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } // lv >>= rv shrAssign(m, lv.TV, rv) if lv.Base != nil { diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 0e8eec9db23..ad41cb7d0ff 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -719,11 +719,11 @@ func addAssign(alloc *Allocator, lv, rv *TypedValue) { case Float64Type: // NOTE: gno doesn't fuse *+. lv.SetFloat64(softfloat.Fadd64(lv.GetFloat64(), rv.GetFloat64())) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Add(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} - case BigdecType, UntypedBigdecType: + case UntypedBigdecType: lb := lv.GetBigDec() rb := rv.GetBigDec() sum := apd.New(0, 0) @@ -775,11 +775,11 @@ func subAssign(lv, rv *TypedValue) { case Float64Type: // NOTE: gno doesn't fuse *+. lv.SetFloat64(softfloat.Fsub64(lv.GetFloat64(), rv.GetFloat64())) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Sub(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} - case BigdecType, UntypedBigdecType: + case UntypedBigdecType: lb := lv.GetBigDec() rb := rv.GetBigDec() diff := apd.New(0, 0) @@ -831,11 +831,11 @@ func mulAssign(lv, rv *TypedValue) { case Float64Type: // NOTE: gno doesn't fuse *+. lv.SetFloat64(softfloat.Fmul64(lv.GetFloat64(), rv.GetFloat64())) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Mul(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} - case BigdecType, UntypedBigdecType: + case UntypedBigdecType: lb := lv.GetBigDec() rb := rv.GetBigDec() prod := apd.New(0, 0) @@ -929,14 +929,14 @@ func quoAssign(lv, rv *TypedValue) *Exception { if ok { lv.SetFloat64(softfloat.Fdiv64(lv.GetFloat64(), rv.GetFloat64())) } - case BigintType, UntypedBigintType: + case UntypedBigintType: if rv.GetBigInt().Sign() == 0 { return expt } lb := lv.GetBigInt() lb = big.NewInt(0).Quo(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} - case BigdecType, UntypedBigdecType: + case UntypedBigdecType: if rv.GetBigDec().Cmp(apd.New(0, 0)) == 0 { return expt } @@ -1022,7 +1022,7 @@ func remAssign(lv, rv *TypedValue) *Exception { return expt } lv.SetUint64(lv.GetUint64() % rv.GetUint64()) - case BigintType, UntypedBigintType: + case UntypedBigintType: if rv.GetBigInt().Sign() == 0 { return expt } @@ -1067,7 +1067,7 @@ func bandAssign(lv, rv *TypedValue) { lv.SetUint32(lv.GetUint32() & rv.GetUint32()) case Uint64Type: lv.SetUint64(lv.GetUint64() & rv.GetUint64()) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).And(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} @@ -1106,7 +1106,7 @@ func bandnAssign(lv, rv *TypedValue) { lv.SetUint32(lv.GetUint32() &^ rv.GetUint32()) case Uint64Type: lv.SetUint64(lv.GetUint64() &^ rv.GetUint64()) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).AndNot(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} @@ -1145,7 +1145,7 @@ func borAssign(lv, rv *TypedValue) { lv.SetUint32(lv.GetUint32() | rv.GetUint32()) case Uint64Type: lv.SetUint64(lv.GetUint64() | rv.GetUint64()) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Or(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} @@ -1184,7 +1184,7 @@ func xorAssign(lv, rv *TypedValue) { lv.SetUint32(lv.GetUint32() ^ rv.GetUint32()) case Uint64Type: lv.SetUint64(lv.GetUint64() ^ rv.GetUint64()) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Xor(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} @@ -1211,8 +1211,8 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { switch baseOf(lv.T) { case IntType: checkOverflow(func() bool { - l := big.NewInt(int64(lv.GetInt())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + l := big.NewInt(lv.GetInt()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt)) != 1 }) @@ -1221,7 +1221,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Int8Type: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt8())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt8)) != 1 }) @@ -1230,7 +1230,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Int16Type: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt16())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt16)) != 1 }) @@ -1239,7 +1239,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Int32Type, UntypedRuneType: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt32())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt32)) != 1 }) @@ -1248,7 +1248,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Int64Type: checkOverflow(func() bool { l := big.NewInt(lv.GetInt64()) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt64)) != 1 }) @@ -1256,8 +1256,8 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { lv.SetInt64(lv.GetInt64() << rv.GetUint()) case UintType: checkOverflow(func() bool { - l := big.NewInt(0).SetUint64(uint64(lv.GetUint())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + l := big.NewInt(0).SetUint64(lv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(0).SetUint64(math.MaxUint)) != 1 }) @@ -1266,7 +1266,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Uint8Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint8())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint8)) != 1 }) @@ -1275,7 +1275,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case DataByteType: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetDataByte())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint8)) != 1 }) @@ -1284,7 +1284,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Uint16Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint16())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint16)) != 1 }) @@ -1293,7 +1293,7 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Uint32Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint32())) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint32)) != 1 }) @@ -1302,15 +1302,15 @@ func shlAssign(m *Machine, lv, rv *TypedValue) { case Uint64Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(lv.GetUint64()) - r := big.NewInt(0).Lsh(l, rv.GetUint()) + r := big.NewInt(0).Lsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(0).SetUint64(math.MaxUint64)) != 1 }) lv.SetUint64(lv.GetUint64() << rv.GetUint()) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() - lb = big.NewInt(0).Lsh(lb, rv.GetUint()) + lb = big.NewInt(0).Lsh(lb, uint(rv.GetUint())) lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( @@ -1335,8 +1335,8 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { switch baseOf(lv.T) { case IntType: checkOverflow(func() bool { - l := big.NewInt(int64(lv.GetInt())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + l := big.NewInt(lv.GetInt()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt)) != 1 }) @@ -1345,7 +1345,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Int8Type: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt8())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt8)) != 1 }) @@ -1354,7 +1354,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Int16Type: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt16())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt16)) != 1 }) @@ -1363,7 +1363,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Int32Type, UntypedRuneType: checkOverflow(func() bool { l := big.NewInt(int64(lv.GetInt32())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt32)) != 1 }) @@ -1372,7 +1372,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Int64Type: checkOverflow(func() bool { l := big.NewInt(lv.GetInt64()) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxInt64)) != 1 }) @@ -1380,8 +1380,8 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { lv.SetInt64(lv.GetInt64() >> rv.GetUint()) case UintType: checkOverflow(func() bool { - l := big.NewInt(0).SetUint64(uint64(lv.GetUint())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + l := big.NewInt(0).SetUint64(lv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(0).SetUint64(math.MaxUint)) != 1 }) @@ -1390,7 +1390,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Uint8Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint8())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint8)) != 1 }) @@ -1399,7 +1399,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case DataByteType: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetDataByte())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint8)) != 1 }) @@ -1408,7 +1408,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Uint16Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint16())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint16)) != 1 }) @@ -1417,7 +1417,7 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Uint32Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(uint64(lv.GetUint32())) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(math.MaxUint32)) != 1 }) @@ -1426,15 +1426,15 @@ func shrAssign(m *Machine, lv, rv *TypedValue) { case Uint64Type: checkOverflow(func() bool { l := big.NewInt(0).SetUint64(lv.GetUint64()) - r := big.NewInt(0).Rsh(l, rv.GetUint()) + r := big.NewInt(0).Rsh(l, uint(rv.GetUint())) return r.Cmp(big.NewInt(0).SetUint64(math.MaxUint64)) != 1 }) lv.SetUint64(lv.GetUint64() >> rv.GetUint()) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() - lb = big.NewInt(0).Rsh(lb, rv.GetUint()) + lb = big.NewInt(0).Rsh(lb, uint(rv.GetUint())) lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index ba5b7507cff..6442b4f4523 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -219,7 +219,7 @@ func (m *Machine) doOpReturn() { if finalize { // Finalize realm updates! // NOTE: This is a resource intensive undertaking. - crlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + crlm.FinalizeRealmTransaction(m.Store) } } // finalize @@ -254,7 +254,7 @@ func (m *Machine) doOpReturnFromBlock() { if finalize { // Finalize realm updates! // NOTE: This is a resource intensive undertaking. - crlm.FinalizeRealmTransaction(m.ReadOnly, m.Store) + crlm.FinalizeRealmTransaction(m.Store) } } // finalize diff --git a/gnovm/pkg/gnolang/op_decl.go b/gnovm/pkg/gnolang/op_decl.go index c9c04ccf76d..6dbae2d3edf 100644 --- a/gnovm/pkg/gnolang/op_decl.go +++ b/gnovm/pkg/gnolang/op_decl.go @@ -29,35 +29,31 @@ func (m *Machine) doOpValueDecl() { } else { tv = rvs[i] } - if nt != nil { - if nt.Kind() == InterfaceKind { - if isUntyped(tv.T) { - ConvertUntypedTo(&tv, nil) - } else { - // keep type as is. + + if isUntyped(tv.T) { + if !s.Const { + if !m.PreprocessorMode && rvs[i].T.Kind() != BoolKind { + panic("untyped conversion should not happen at runtime") } - } else { - if isUntyped(tv.T) { - ConvertUntypedTo(&tv, nt) - } else { - if debug { - if nt.TypeID() != tv.T.TypeID() && - baseOf(nt).TypeID() != tv.T.TypeID() { - panic(fmt.Sprintf( - "type mismatch: %s vs %s", - nt.TypeID(), - tv.T.TypeID(), - )) - } + ConvertUntypedTo(&tv, nil) + } + } else if nt != nil { + // if nt.T is an interface, maintain tv.T as-is. + if nt.Kind() != InterfaceKind { + if debug { + if nt.TypeID() != tv.T.TypeID() && + baseOf(nt).TypeID() != tv.T.TypeID() { + panic(fmt.Sprintf( + "type mismatch: %s vs %s", + nt.TypeID(), + tv.T.TypeID(), + )) } - tv.T = nt } + tv.T = nt } - } else if s.Const { - // leave untyped as is. - } else if isUntyped(tv.T) { - ConvertUntypedTo(&tv, nil) } + nx := &s.NameExprs[i] ptr := lb.GetPointerToMaybeHeapDefine(m.Store, nx) ptr.Assign2(m.Alloc, m.Store, m.Realm, tv, false) diff --git a/gnovm/pkg/gnolang/op_exec.go b/gnovm/pkg/gnolang/op_exec.go index 5f71ffefa0c..d81c15956d2 100644 --- a/gnovm/pkg/gnolang/op_exec.go +++ b/gnovm/pkg/gnolang/op_exec.go @@ -166,7 +166,7 @@ func (m *Machine) doOpExec(op Op) { case -1: // assign list element. if bs.Key != nil { iv := TypedValue{T: IntType} - iv.SetInt(bs.ListIndex) + iv.SetInt(int64(bs.ListIndex)) switch bs.Op { case ASSIGN: m.PopAsPointer(bs.Key).Assign2(m.Alloc, m.Store, m.Realm, iv, false) @@ -180,7 +180,7 @@ func (m *Machine) doOpExec(op Op) { } if bs.Value != nil { iv := TypedValue{T: IntType} - iv.SetInt(bs.ListIndex) + iv.SetInt(int64(bs.ListIndex)) ev := xv.GetPointerAtIndex(m.Alloc, m.Store, &iv).Deref() switch bs.Op { case ASSIGN: @@ -262,7 +262,7 @@ func (m *Machine) doOpExec(op Op) { case -1: // assign list element. if bs.Key != nil { iv := TypedValue{T: IntType} - iv.SetInt(bs.ListIndex) + iv.SetInt(int64(bs.ListIndex)) switch bs.Op { case ASSIGN: m.PopAsPointer(bs.Key).Assign2(m.Alloc, m.Store, m.Realm, iv, false) @@ -904,7 +904,7 @@ func (m *Machine) doOpSwitchClause() { // caiv := m.PeekValue(2) // switch clause case index (reuse) cliv := m.PeekValue(3) // switch clause index (reuse) idx := cliv.GetInt() - if idx >= len(ss.Clauses) { + if int(idx) >= len(ss.Clauses) { // no clauses matched: do nothing. m.PopStmt() // pop switch stmt m.PopValue() // pop switch tag value @@ -980,7 +980,7 @@ func (m *Machine) doOpSwitchClauseCase() { clidx := cliv.GetInt() cl := ss.Clauses[clidx] caidx := caiv.GetInt() - if (caidx + 1) < len(cl.Cases) { + if int(caidx+1) < len(cl.Cases) { // try next clause case. m.PushOp(OpSwitchClauseCase) // TODO consider sticky caiv.SetInt(caidx + 1) diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index c0f6225740b..475a91580ad 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -91,15 +91,15 @@ func (m *Machine) doOpSlice() { var lowVal, highVal, maxVal int = -1, -1, -1 // max if sx.Max != nil { - maxVal = m.PopValue().ConvertGetInt() + maxVal = int(m.PopValue().ConvertGetInt()) } // high if sx.High != nil { - highVal = m.PopValue().ConvertGetInt() + highVal = int(m.PopValue().ConvertGetInt()) } // low if sx.Low != nil { - lowVal = m.PopValue().ConvertGetInt() + lowVal = int(m.PopValue().ConvertGetInt()) } else { lowVal = 0 } @@ -513,7 +513,7 @@ func (m *Machine) doOpArrayLit() { al, ad := av.List, av.Data vs := m.PopValues(ne) set := make([]bool, bt.Len) - idx := 0 + var idx int64 for i, v := range vs { if kx := x.Elts[i].Key; kx != nil { // XXX why convert? @@ -593,7 +593,7 @@ func (m *Machine) doOpSliceLit2() { // peek slice type. st := m.PeekValue(1).V.(TypeValue).Type // calculate maximum index. - maxVal := 0 + var maxVal int64 for i := 0; i < el; i++ { itv := tvs[i*2+0] idx := itv.ConvertGetInt() diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index 708aae821ac..ee0e90a94a5 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -58,11 +58,11 @@ func (m *Machine) doOpInc() { lv.SetFloat32(softfloat.Fadd32(lv.GetFloat32(), softfloat.Fintto32(1))) case Float64Type: lv.SetFloat64(softfloat.Fadd64(lv.GetFloat64(), softfloat.Fintto64(1))) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Add(lb, big.NewInt(1)) lv.V = BigintValue{V: lb} - case BigdecType, UntypedBigdecType: + case UntypedBigdecType: lb := lv.GetBigDec() sum := apd.New(0, 0) cond, err := apd.BaseContext.WithPrecision(0).Add(sum, lb, apd.New(1, 0)) @@ -128,11 +128,11 @@ func (m *Machine) doOpDec() { lv.SetFloat32(softfloat.Fsub32(lv.GetFloat32(), softfloat.Fintto32(1))) case Float64Type: lv.SetFloat64(softfloat.Fsub64(lv.GetFloat64(), softfloat.Fintto64(1))) - case BigintType, UntypedBigintType: + case UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Sub(lb, big.NewInt(1)) lv.V = BigintValue{V: lb} - case BigdecType, UntypedBigdecType: + case UntypedBigdecType: lb := lv.GetBigDec() sum := apd.New(0, 0) cond, err := apd.BaseContext.WithPrecision(0).Sub(sum, lb, apd.New(1, 0)) diff --git a/gnovm/pkg/gnolang/op_string.go b/gnovm/pkg/gnolang/op_string.go deleted file mode 100644 index b13bb8f278e..00000000000 --- a/gnovm/pkg/gnolang/op_string.go +++ /dev/null @@ -1,178 +0,0 @@ -// Code generated by "stringer -type=Op ./pkg/gnolang"; DO NOT EDIT. - -package gnolang - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[OpInvalid-0] - _ = x[OpHalt-1] - _ = x[OpNoop-2] - _ = x[OpExec-3] - _ = x[OpPrecall-4] - _ = x[OpCall-5] - _ = x[OpCallNativeBody-6] - _ = x[OpReturn-7] - _ = x[OpReturnFromBlock-8] - _ = x[OpReturnToBlock-9] - _ = x[OpDefer-10] - _ = x[OpCallDeferNativeBody-11] - _ = x[OpGo-12] - _ = x[OpSelect-13] - _ = x[OpSwitchClause-14] - _ = x[OpSwitchClauseCase-15] - _ = x[OpTypeSwitch-16] - _ = x[OpIfCond-17] - _ = x[OpPopValue-18] - _ = x[OpPopResults-19] - _ = x[OpPopBlock-20] - _ = x[OpPopFrameAndReset-21] - _ = x[OpPanic1-22] - _ = x[OpPanic2-23] - _ = x[OpUpos-32] - _ = x[OpUneg-33] - _ = x[OpUnot-34] - _ = x[OpUxor-35] - _ = x[OpUrecv-37] - _ = x[OpLor-38] - _ = x[OpLand-39] - _ = x[OpEql-40] - _ = x[OpNeq-41] - _ = x[OpLss-42] - _ = x[OpLeq-43] - _ = x[OpGtr-44] - _ = x[OpGeq-45] - _ = x[OpAdd-46] - _ = x[OpSub-47] - _ = x[OpBor-48] - _ = x[OpXor-49] - _ = x[OpMul-50] - _ = x[OpQuo-51] - _ = x[OpRem-52] - _ = x[OpShl-53] - _ = x[OpShr-54] - _ = x[OpBand-55] - _ = x[OpBandn-56] - _ = x[OpEval-64] - _ = x[OpBinary1-65] - _ = x[OpIndex1-66] - _ = x[OpIndex2-67] - _ = x[OpSelector-68] - _ = x[OpSlice-69] - _ = x[OpStar-70] - _ = x[OpRef-71] - _ = x[OpTypeAssert1-72] - _ = x[OpTypeAssert2-73] - _ = x[OpStaticTypeOf-74] - _ = x[OpCompositeLit-75] - _ = x[OpArrayLit-76] - _ = x[OpSliceLit-77] - _ = x[OpSliceLit2-78] - _ = x[OpMapLit-79] - _ = x[OpStructLit-80] - _ = x[OpFuncLit-81] - _ = x[OpConvert-82] - _ = x[OpArrayLitGoNative-96] - _ = x[OpSliceLitGoNative-97] - _ = x[OpStructLitGoNative-98] - _ = x[OpCallGoNative-99] - _ = x[OpFieldType-112] - _ = x[OpArrayType-113] - _ = x[OpSliceType-114] - _ = x[OpPointerType-115] - _ = x[OpInterfaceType-116] - _ = x[OpChanType-117] - _ = x[OpFuncType-118] - _ = x[OpMapType-119] - _ = x[OpStructType-120] - _ = x[OpMaybeNativeType-121] - _ = x[OpAssign-128] - _ = x[OpAddAssign-129] - _ = x[OpSubAssign-130] - _ = x[OpMulAssign-131] - _ = x[OpQuoAssign-132] - _ = x[OpRemAssign-133] - _ = x[OpBandAssign-134] - _ = x[OpBandnAssign-135] - _ = x[OpBorAssign-136] - _ = x[OpXorAssign-137] - _ = x[OpShlAssign-138] - _ = x[OpShrAssign-139] - _ = x[OpDefine-140] - _ = x[OpInc-141] - _ = x[OpDec-142] - _ = x[OpValueDecl-144] - _ = x[OpTypeDecl-145] - _ = x[OpSticky-208] - _ = x[OpBody-209] - _ = x[OpForLoop-210] - _ = x[OpRangeIter-211] - _ = x[OpRangeIterString-212] - _ = x[OpRangeIterMap-213] - _ = x[OpRangeIterArrayPtr-214] - _ = x[OpReturnCallDefers-215] - _ = x[OpVoid-255] -} - -const ( - _Op_name_0 = "OpInvalidOpHaltOpNoopOpExecOpPrecallOpCallOpCallNativeBodyOpReturnOpReturnFromBlockOpReturnToBlockOpDeferOpCallDeferNativeBodyOpGoOpSelectOpSwitchClauseOpSwitchClauseCaseOpTypeSwitchOpIfCondOpPopValueOpPopResultsOpPopBlockOpPopFrameAndResetOpPanic1OpPanic2" - _Op_name_1 = "OpUposOpUnegOpUnotOpUxor" - _Op_name_2 = "OpUrecvOpLorOpLandOpEqlOpNeqOpLssOpLeqOpGtrOpGeqOpAddOpSubOpBorOpXorOpMulOpQuoOpRemOpShlOpShrOpBandOpBandn" - _Op_name_3 = "OpEvalOpBinary1OpIndex1OpIndex2OpSelectorOpSliceOpStarOpRefOpTypeAssert1OpTypeAssert2OpStaticTypeOfOpCompositeLitOpArrayLitOpSliceLitOpSliceLit2OpMapLitOpStructLitOpFuncLitOpConvert" - _Op_name_4 = "OpArrayLitGoNativeOpSliceLitGoNativeOpStructLitGoNativeOpCallGoNative" - _Op_name_5 = "OpFieldTypeOpArrayTypeOpSliceTypeOpPointerTypeOpInterfaceTypeOpChanTypeOpFuncTypeOpMapTypeOpStructTypeOpMaybeNativeType" - _Op_name_6 = "OpAssignOpAddAssignOpSubAssignOpMulAssignOpQuoAssignOpRemAssignOpBandAssignOpBandnAssignOpBorAssignOpXorAssignOpShlAssignOpShrAssignOpDefineOpIncOpDec" - _Op_name_7 = "OpValueDeclOpTypeDecl" - _Op_name_8 = "OpStickyOpBodyOpForLoopOpRangeIterOpRangeIterStringOpRangeIterMapOpRangeIterArrayPtrOpReturnCallDefers" - _Op_name_9 = "OpVoid" -) - -var ( - _Op_index_0 = [...]uint16{0, 9, 15, 21, 27, 36, 42, 58, 66, 83, 98, 105, 126, 130, 138, 152, 170, 182, 190, 200, 212, 222, 240, 248, 256} - _Op_index_1 = [...]uint8{0, 6, 12, 18, 24} - _Op_index_2 = [...]uint8{0, 7, 12, 18, 23, 28, 33, 38, 43, 48, 53, 58, 63, 68, 73, 78, 83, 88, 93, 99, 106} - _Op_index_3 = [...]uint8{0, 6, 15, 23, 31, 41, 48, 54, 59, 72, 85, 99, 113, 123, 133, 144, 152, 163, 172, 181} - _Op_index_4 = [...]uint8{0, 18, 36, 55, 69} - _Op_index_5 = [...]uint8{0, 11, 22, 33, 46, 61, 71, 81, 90, 102, 119} - _Op_index_6 = [...]uint8{0, 8, 19, 30, 41, 52, 63, 75, 88, 99, 110, 121, 132, 140, 145, 150} - _Op_index_7 = [...]uint8{0, 11, 21} - _Op_index_8 = [...]uint8{0, 8, 14, 23, 34, 51, 65, 84, 102} -) - -func (i Op) String() string { - switch { - case i <= 23: - return _Op_name_0[_Op_index_0[i]:_Op_index_0[i+1]] - case 32 <= i && i <= 35: - i -= 32 - return _Op_name_1[_Op_index_1[i]:_Op_index_1[i+1]] - case 37 <= i && i <= 56: - i -= 37 - return _Op_name_2[_Op_index_2[i]:_Op_index_2[i+1]] - case 64 <= i && i <= 82: - i -= 64 - return _Op_name_3[_Op_index_3[i]:_Op_index_3[i+1]] - case 96 <= i && i <= 99: - i -= 96 - return _Op_name_4[_Op_index_4[i]:_Op_index_4[i+1]] - case 112 <= i && i <= 121: - i -= 112 - return _Op_name_5[_Op_index_5[i]:_Op_index_5[i+1]] - case 128 <= i && i <= 142: - i -= 128 - return _Op_name_6[_Op_index_6[i]:_Op_index_6[i+1]] - case 144 <= i && i <= 145: - i -= 144 - return _Op_name_7[_Op_index_7[i]:_Op_index_7[i+1]] - case 208 <= i && i <= 215: - i -= 208 - return _Op_name_8[_Op_index_8[i]:_Op_index_8[i+1]] - case i == 255: - return _Op_name_9 - default: - return "Op(" + strconv.FormatInt(int64(i), 10) + ")" - } -} diff --git a/gnovm/pkg/gnolang/op_types.go b/gnovm/pkg/gnolang/op_types.go index 37b549fe14c..640ddc7d9a9 100644 --- a/gnovm/pkg/gnolang/op_types.go +++ b/gnovm/pkg/gnolang/op_types.go @@ -47,7 +47,7 @@ func (m *Machine) doOpArrayType() { panic("unexpected untyped const type for array type len during runtime") } } - t.Len = lv.GetInt() // TODO lazy convert? + t.Len = int(lv.GetInt()) // TODO lazy convert? } tv := m.PeekValue(1) // re-use t.Elt = tv.GetType() @@ -85,8 +85,9 @@ func (m *Machine) doOpFuncType() { } // Push func type. ft := &FuncType{ - Params: params, - Results: results, + Params: params, + Results: results, + IsClosure: x.IsClosure, } m.PushValue(TypedValue{ T: gTypeType, @@ -238,7 +239,7 @@ func (m *Machine) doOpStaticTypeOf() { dxt = xt case 1: dxt = baseOf(xt) - path.Depth = 0 + path.SetDepth(0) default: panic("should not happen") } @@ -246,16 +247,16 @@ func (m *Machine) doOpStaticTypeOf() { switch path.Depth { case 0: dxt = xt.Elem() - path.Depth = 0 + path.SetDepth(0) case 1: dxt = xt.Elem() - path.Depth = 0 + path.SetDepth(0) case 2: dxt = baseOf(xt.Elem()) - path.Depth = 0 + path.SetDepth(0) case 3: dxt = baseOf(xt.Elem()) - path.Depth = 0 + path.SetDepth(0) default: panic("should not happen") } @@ -264,19 +265,19 @@ func (m *Machine) doOpStaticTypeOf() { case 0: dxt = xt.Elem() path.Type = VPField - path.Depth = 0 + path.SetDepth(0) case 1: dxt = xt.Elem() path.Type = VPField - path.Depth = 0 + path.SetDepth(0) case 2: dxt = baseOf(xt.Elem()) path.Type = VPField - path.Depth = 0 + path.SetDepth(0) case 3: dxt = baseOf(xt.Elem()) path.Type = VPField - path.Depth = 0 + path.SetDepth(0) default: panic("should not happen") } diff --git a/gnovm/pkg/gnolang/op_unary.go b/gnovm/pkg/gnolang/op_unary.go index 469c80b8dac..73d83e32983 100644 --- a/gnovm/pkg/gnolang/op_unary.go +++ b/gnovm/pkg/gnolang/op_unary.go @@ -50,10 +50,10 @@ func (m *Machine) doOpUneg() { xv.SetFloat32(softfloat.Fneg32(xv.GetFloat32())) case Float64Type: xv.SetFloat64(softfloat.Fneg64(xv.GetFloat64())) - case UntypedBigintType, BigintType: + case UntypedBigintType: bv := xv.V.(BigintValue) xv.V = BigintValue{V: new(big.Int).Neg(bv.V)} - case UntypedBigdecType, BigdecType: + case UntypedBigdecType: bv := xv.V.(BigdecValue) xv.V = BigdecValue{V: apd.New(0, 0).Neg(bv.V)} case nil: @@ -112,9 +112,9 @@ func (m *Machine) doOpUxor() { xv.SetUint32(^xv.GetUint32()) case Uint64Type: xv.SetUint64(^xv.GetUint64()) - case UntypedBigintType, BigintType: - // XXX can it even be implemented? - panic("not yet implemented") + case UntypedBigintType: + bv := xv.V.(BigintValue) + xv.V = BigintValue{V: new(big.Int).Not(bv.V)} default: panic(fmt.Sprintf("unexpected type %s in operation", baseOf(xv.T))) diff --git a/gnovm/pkg/gnolang/ownership.go b/gnovm/pkg/gnolang/ownership.go index 511b44bfc73..da254d383a9 100644 --- a/gnovm/pkg/gnolang/ownership.go +++ b/gnovm/pkg/gnolang/ownership.go @@ -133,17 +133,29 @@ var ( ) type ObjectInfo struct { - ID ObjectID // set if real. - Hash ValueHash `json:",omitempty"` // zero if dirty. - OwnerID ObjectID `json:",omitempty"` // parent in the ownership tree. - ModTime uint64 // time last updated. - RefCount int // for persistence. deleted/gc'd if 0. - IsEscaped bool `json:",omitempty"` // hash in iavl. + ID ObjectID // set if real. + Hash ValueHash `json:",omitempty"` // zero if dirty. + OwnerID ObjectID `json:",omitempty"` // parent in the ownership tree. + ModTime uint64 // time last updated. + RefCount int // for persistence. deleted/gc'd if 0. + + // Object has multiple references (refcount > 1) and is persisted separately + IsEscaped bool `json:",omitempty"` // hash in iavl. + // MemRefCount int // consider for optimizations. - isDirty bool - isDeleted bool - isNewReal bool + // Object has been modified and needs to be saved + isDirty bool + + // Object has been permanently deleted + isDeleted bool + + // Object is newly created in current transaction and will be persisted + isNewReal bool + + // Object newly created multiple references in current transaction isNewEscaped bool + + // Object is marked for deletion in current transaction isNewDeleted bool // XXX huh? @@ -294,7 +306,13 @@ func (oi *ObjectInfo) SetIsDeleted(x bool, mt uint64) { // NOTE: Don't over-write modtime. // Consider adding a DelTime, or just log it somewhere, or // continue to ignore it. - oi.isDirty = x + + // The above comment is likely made because it could introduce complexity + // Objects can be "undeleted" if referenced during a transaction + // If an object is deleted and then undeleted in the same transaction + // If an object is deleted multiple times + // ie...continue to ignore it + oi.isDeleted = x } func (oi *ObjectInfo) GetIsNewReal() bool { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index ffa0f518331..10f7cdbe474 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -129,6 +129,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { continue } + d.SetAttribute(ATTR_GLOBAL, true) // recursively predefine dependencies. d2, _ := predefineNow(store, fn, d) @@ -233,6 +234,7 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) { nx := &n.NameExpr nx.Type = NameExprTypeDefine pkg.Predefine(false, n.Name) + pkg.UnassignableNames = append(pkg.UnassignableNames, n.Name) } case *FuncTypeExpr: for i := range n.Params { @@ -516,6 +518,17 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { if cd, ok := d.(*ValueDecl); ok { checkValDefineMismatch(cd) } + + isGlobal := true + + for i := len(ns) - 1; i > 0; i-- { + if _, ok := ns[i].(*FuncDecl); ok { + isGlobal = false + } + } + + d.SetAttribute(ATTR_GLOBAL, isGlobal) + // recursively predefine dependencies. d2, ppd := predefineNow(store, last, d) if ppd { @@ -1359,6 +1372,11 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { n.NumArgs = 1 ct := evalStaticType(store, last, n.Func) at := evalStaticTypeOf(store, last, n.Args[0]) + + if _, isIface := baseOf(ct).(*InterfaceType); isIface { + assertAssignableTo(n, at, ct, false) + } + var constConverted bool switch arg0 := n.Args[0].(type) { case *ConstExpr: @@ -1389,6 +1407,19 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { if !constConverted { convertConst(store, last, n, arg0, nil) } + + // check legal type for nil + if arg0.IsUndefined() { + switch ct.Kind() { // special case for nil conversion check. + case SliceKind, PointerKind, FuncKind, MapKind, InterfaceKind, ChanKind: + convertConst(store, last, n, arg0, ct) + default: + panic(fmt.Sprintf( + "cannot convert %v to %v", + arg0, ct.Kind())) + } + } + // evaluate the new expression. cx := evalConst(store, last, n) // Though cx may be undefined if ct is interface, @@ -1410,7 +1441,16 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { checkOrConvertType(store, last, n, &n.Args[0], ct, false) } default: - // do nothing + ctBase := baseOf(ct) + atBase := baseOf(at) + + _, isCTInterface := ctBase.(*InterfaceType) + _, isCTNative := ctBase.(*NativeType) + _, isATInterface := atBase.(*InterfaceType) + + if (!isCTInterface && !isCTNative) && isATInterface { + panic(fmt.Sprintf("cannot convert %v to %v: need type assertion", at.TypeID(), ct.TypeID())) + } } // general case, for non-const untyped && no nested untyped shift // after handling const, and special cases recursively, set the target node type @@ -1430,6 +1470,8 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { if cx, ok := n.Func.(*ConstExpr); ok { fv := cx.GetFunc() if fv.PkgPath == uversePkgPath && fv.Name == "append" { + // append returns a slice and slices are always addressable. + n.Addressability = addressabilityStatusSatisfied if n.Varg && len(n.Args) == 2 { // If the second argument is a string, // convert to byteslice. @@ -1476,9 +1518,23 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { n.Args[1] = args1 } } + } else if fv.PkgPath == uversePkgPath && fv.Name == "new" { + // The pointer value returned is not addressable, but maybe some selector + // will make it addressable. For now mark it as not addressable. + n.Addressability = addressabilityStatusUnsatisfied } } + // If addressability is not satisfied at this point and the function call returns only one + // result, then mark addressability as unsatisfied. Otherwise, this expression has already + // been explicitly marked as satisfied, or the function returns multiple results, rendering + // addressability NotApplicable for this situation -- it should fallback to the error produced + // when trying to take a reference or slice the result of a call expression that returns + // multiple values. + if n.Addressability != addressabilityStatusSatisfied && len(ft.Results) == 1 { + n.Addressability = addressabilityStatusUnsatisfied + } + // Continue with general case. hasVarg := ft.HasVarg() isVarg := n.Varg @@ -1624,9 +1680,23 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // Replace const index with int *ConstExpr, // or if not const, assert integer type.. checkOrConvertIntegerKind(store, last, n, n.Index) + + // Addressability of this index expression can only be known for slice and + // strings, explanations below in the respective blocks. If this is an index + // on an array, do nothing. This will defer to the array's addresability when + // the `addressability` method is called on this index expression. + if dt.Kind() == SliceKind { + // A value at a slice index is always addressable because the underlying + // array is addressable. + n.Addressability = addressabilityStatusSatisfied + } else if dt.Kind() == StringKind { + // Special case; string indexes are never addressable. + n.Addressability = addressabilityStatusUnsatisfied + } case MapKind: mt := baseOf(gnoTypeOf(store, dt)).(*MapType) checkOrConvertType(store, last, n, &n.Index, mt.Key, false) + n.Addressability = addressabilityStatusUnsatisfied default: panic(fmt.Sprintf( "unexpected index base kind for type %s", @@ -1641,8 +1711,14 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { checkOrConvertIntegerKind(store, last, n, n.High) checkOrConvertIntegerKind(store, last, n, n.Max) - // if n.X is untyped, convert to corresponding type t := evalStaticTypeOf(store, last, n.X) + if t.Kind() == ArrayKind { + if n.X.addressability() == addressabilityStatusUnsatisfied { + panic(fmt.Sprintf("cannot take address of %s", n.X.String())) + } + } + + // if n.X is untyped, convert to corresponding type if isUntyped(t) { dt := defaultTypeOf(t) checkOrConvertType(store, last, n, &n.X, dt, false) @@ -1681,8 +1757,6 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { ) } - evalStaticType(store, last, n.Type) - // TRANS_LEAVE ----------------------- case *UnaryExpr: xt := evalStaticTypeOf(store, last, n.X) @@ -1742,6 +1816,10 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { convertType(store, last, n, &n.Elts[i].Key, IntType) checkOrConvertType(store, last, n, &n.Elts[i].Value, cclt.Elt, false) } + + // Slices are always addressable because the underlying array + // is added to the heap during initialization. + n.IsAddressable = true case *MapType: for i := 0; i < len(n.Elts); i++ { checkOrConvertType(store, last, n, &n.Elts[i].Key, cclt.Key, false) @@ -1763,7 +1841,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { if elt.Key == nil { idx++ } else { - k := evalConst(store, last, elt.Key).ConvertGetInt() + k := int(evalConst(store, last, elt.Key).ConvertGetInt()) if idx <= k { idx = k + 1 } else { @@ -1776,11 +1854,21 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // at.Vrd = false at.Len = idx // update node - cx := constInt(n, idx) + cx := constInt(n, int64(idx)) n.Type.(*ArrayTypeExpr).Len = cx } } + // If ftype is TRANS_REF_X, then this composite literal being created looks + // something like this in the code: `&MyStruct{}`. It is marked as addressable here + // because on TRANS_LEAVE for a RefExpr, it defers to the addressability of the + // expression it is referencing. When a composite literal is created with a preceding + // '&', it means the value is assigned to an address and that address is returned, + // so the value is addressable. + if ftype == TRANS_REF_X { + n.IsAddressable = true + } + // TRANS_LEAVE ----------------------- case *KeyValueExpr: // NOTE: For simplicity we just @@ -1797,6 +1885,9 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *SelectorExpr: xt := evalStaticTypeOf(store, last, n.X) + if xt.Kind() == PointerKind { + n.IsAddressable = true + } // Set selector path based on xt's type. switch cxt := xt.(type) { @@ -1809,6 +1900,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { panic(fmt.Sprintf("missing field %s in %s", n.Sel, cxt.String())) } + if len(tr) > 1 { // (the last vp, tr[len(tr)-1], is for n.Sel) if debug { @@ -1911,10 +2003,13 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // packages may contain constant vars, // so check and evaluate if so. tt := pn.GetStaticTypeOfAt(store, n.Path) - if isUntyped(tt) { + + // Produce a constant expression for both typed and untyped constants. + if isUntyped(tt) || pn.GetIsConstAt(store, n.Path) { cx := evalConst(store, last, n) return cx, TRANS_CONTINUE } + case *TypeType: // unbound method xt := evalStaticType(store, last, n.X) @@ -1989,6 +2084,15 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *AssignStmt: n.AssertCompatible(store, last) + if n.Op == ASSIGN { + for _, lh := range n.Lhs { + if ne, ok := lh.(*NameExpr); ok { + if !last.GetStaticBlock().IsAssignable(store, ne.Name) { + panic("not assignable") + } + } + } + } // NOTE: keep DEFINE and ASSIGN in sync. if n.Op == DEFINE { @@ -2366,9 +2470,20 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // We need to replace all references of the new // Type with old Type, including in attributes. n.Type.SetAttribute(ATTR_TYPE_VALUE, dst) - // Replace the type with *constTypeExpr{}, + // Replace the type with *{}, // otherwise methods would be un at runtime. n.Type = constType(n.Type, dst) + + case *RefExpr: + // If n.X is a RefExpr, then this expression is something like: + // &(&value). The resulting pointer value of the first reference is not + // addressable. Otherwise fall back to the target expression's addressability. + _, xIsRef := n.X.(*RefExpr) + tt := evalStaticTypeOf(store, last, n.X) + + if ft, is_func := tt.(*FuncType); (is_func && !ft.IsClosure) || xIsRef || n.X.addressability() == addressabilityStatusUnsatisfied { + panic(fmt.Sprintf("cannot take address of %s", n.X.String())) + } } // end type switch statement // END TRANS_LEAVE ----------------------- @@ -2798,7 +2913,7 @@ func findLoopUses1(ctx BlockNode, bn BlockNode) { idx := addHeapCapture(dbn, fle, n.Name) // adjust NameExpr type. n.Type = NameExprTypeHeapUse - n.Path.Depth = uint8(depth) + n.Path.SetDepth(uint8(depth)) n.Path.Index = idx } else { if ftype == TRANS_REF_X { @@ -2894,7 +3009,7 @@ func addHeapCapture(dbn BlockNode, fle *FuncLitExpr, name Name) (idx uint16) { // add name to fle.HeapCaptures. vp := fle.GetPathForName(nil, name) - vp.Depth -= 1 // minus 1 for fle itself. + vp.SetDepth(vp.Depth - 1) // minus 1 for fle itself. ne := NameExpr{ Path: vp, Name: name, @@ -3278,18 +3393,42 @@ func getResultTypedValues(cx *CallExpr) []TypedValue { // NOTE: Generally, conversion happens in a separate step while leaving // composite exprs/nodes that contain constant expression nodes (e.g. const // exprs in the rhs of AssignStmts). +// +// Array-related expressions like `len` and `cap` are manually evaluated +// as constants, even if the array itself is not a constant. This evaluation +// is handled independently of the rest of the constant evaluation process, +// bypassing machine.EvalStatic. func evalConst(store Store, last BlockNode, x Expr) *ConstExpr { // TODO: some check or verification for ensuring x - // is constant? From the machine? - m := NewMachine(".dontcare", store) - m.PreprocessorMode = true + var cx *ConstExpr + if clx, ok := x.(*CallExpr); ok { + t := evalStaticTypeOf(store, last, clx.Args[0]) + if ar, ok := unwrapPointerType(baseOf(t)).(*ArrayType); ok { + fv := clx.Func.(*ConstExpr).V.(*FuncValue) + switch fv.Name { + case "cap", "len": + tv := TypedValue{T: IntType} + tv.SetInt(int64(ar.Len)) + cx = &ConstExpr{ + Source: x, + TypedValue: tv, + } + default: + panic(fmt.Sprintf("unexpected const func %s", fv.Name)) + } + } + } - cv := m.EvalStatic(last, x) - m.PreprocessorMode = false - m.Release() - cx := &ConstExpr{ - Source: x, - TypedValue: cv, + if cx == nil { + // is constant? From the machine? + m := NewMachine(".dontcare", store) + cv := m.EvalStatic(last, x) + m.PreprocessorMode = false + m.Release() + cx = &ConstExpr{ + Source: x, + TypedValue: cv, + } } cx.SetLine(x.GetLine()) cx.SetAttribute(ATTR_PREPROCESSED, true) @@ -3779,73 +3918,336 @@ func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, // type expressions, which must get preprocessed for inner // composite type eliding to work. func findUndefined(store Store, last BlockNode, x Expr) (un Name) { - return findUndefined2(store, last, x, nil) + return findUndefined2(store, last, x, nil, true) +} + +// finds the next undefined identifier and returns it if it is global +func findUndefined2SkipLocals(store Store, last BlockNode, x Expr, t Type) Name { + name := findUndefinedGlobal(store, last, x, t) + + if name == "" { + return "" + } + + existsLocal := func(name Name, bn BlockNode) bool { + curr := bn + for { + currNames := curr.GetBlockNames() + + for _, currName := range currNames { + if currName == name { + return true + } + } + + newcurr := bn.GetStaticBlock().GetParentNode(store) + + if curr == newcurr { + return false + } + + curr = newcurr + + if curr == nil { + return false + } + + _, isFile := curr.(*FileNode) + + if isFile { + return false + } + } + } + + pkg := packageOf(last) + + if _, _, ok := pkg.FileSet.GetDeclForSafe(name); !ok { + return "" + } + + isLocal := existsLocal(name, last) + + if isLocal { + return "" + } + + return name +} + +func findUndefinedStmt(store Store, last BlockNode, stmt Stmt, t Type) Name { + switch s := stmt.(type) { + case *TypeDecl: + un := findUndefined2SkipLocals(store, last, s.Type, t) + + if un != "" { + return un + } + case *ValueDecl: + un := findUndefined2SkipLocals(store, last, s.Type, t) + + if un != "" { + return un + } + for _, rh := range s.Values { + un := findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + case *DeclStmt: + for _, rh := range s.Body { + un := findUndefinedStmt(store, last, rh, t) + + if un != "" { + return un + } + } + case *IncDecStmt: + un := findUndefined2SkipLocals(store, last, s.X, t) + + if un != "" { + return un + } + case *PanicStmt: + un := findUndefined2SkipLocals(store, last, s.Exception, t) + + if un != "" { + return un + } + case *BlockStmt: + for _, rh := range s.Body { + un := findUndefinedStmt(store, s, rh, t) + + if un != "" { + return un + } + } + case *DeferStmt: + un := findUndefined2SkipLocals(store, last, s.Call.Func, t) + + if un != "" { + return un + } + + for _, rh := range s.Call.Args { + un = findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + case *SwitchStmt: + un := findUndefined2SkipLocals(store, last, s.X, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, last, s.Init, t) + if un != "" { + return un + } + + for _, b := range s.Clauses { + b := b + un = findUndefinedStmt(store, s, &b, t) + + if un != "" { + return un + } + } + case *SwitchClauseStmt: + for _, rh := range s.Cases { + un := findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + + if un != "" { + return un + } + } + + case *ExprStmt: + return findUndefined2SkipLocals(store, last, s.X, t) + case *AssignStmt: + for _, rh := range s.Rhs { + un := findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + case *IfStmt: + un := findUndefinedStmt(store, last, s.Init, t) + if un != "" { + return un + } + + un = findUndefined2SkipLocals(store, last, s.Cond, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, last, &s.Else, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, last, &s.Then, t) + if un != "" { + return un + } + case *IfCaseStmt: + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + + if un != "" { + return un + } + } + case *ReturnStmt: + for _, b := range s.Results { + un := findUndefined2SkipLocals(store, last, b, t) + if un != "" { + return un + } + } + case *RangeStmt: + un := findUndefined2SkipLocals(store, last, s.X, t) + if un != "" { + return un + } + + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + if un != "" { + return un + } + } + case *ForStmt: + un := findUndefinedStmt(store, s, s.Init, t) + if un != "" { + return un + } + + un = findUndefined2SkipLocals(store, s, s.Cond, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, s, s.Post, t) + if un != "" { + return un + } + + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + if un != "" { + return un + } + } + case *BranchStmt: + case nil: + return "" + default: + panic(fmt.Sprintf("findUndefinedStmt: %T not supported", s)) + } + return "" +} + +func getGlobalValueRef(sb BlockNode, store Store, n Name) *TypedValue { + sbb := sb.GetStaticBlock() + idx, ok := sb.GetLocalIndex(n) + bb := &sb.GetStaticBlock().Block + bp := sb.GetParentNode(store) + + for { + if ok && sbb.Types[idx] != nil && (bp == nil || bp.GetParentNode(store) == nil) { + return bb.GetPointerToInt(store, int(idx)).TV + } else if bp != nil { + idx, ok = bp.GetLocalIndex(n) + sbb = bp.GetStaticBlock() + bb = sbb.GetBlock() + bp = bp.GetParentNode(store) + } else { + return nil + } + } } -func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { +func findUndefinedGlobal(store Store, last BlockNode, x Expr, t Type) (un Name) { if x == nil { return } switch cx := x.(type) { case *NameExpr: - if tv := last.GetValueRef(store, cx.Name, true); tv != nil { + if tv := getGlobalValueRef(last, store, cx.Name); tv != nil { return } + if _, ok := UverseNode().GetLocalIndex(cx.Name); ok { // XXX NOTE even if the name is shadowed by a file // level declaration, it is fine to return here as it // will be predefined later. return } + return cx.Name case *BasicLitExpr: return case *BinaryExpr: - un = findUndefined(store, last, cx.Left) + un = findUndefinedGlobal(store, last, cx.Left, nil) if un != "" { return } - un = findUndefined(store, last, cx.Right) + un = findUndefinedGlobal(store, last, cx.Right, nil) if un != "" { return } case *SelectorExpr: - return findUndefined(store, last, cx.X) + return findUndefinedGlobal(store, last, cx.X, nil) case *SliceExpr: - un = findUndefined(store, last, cx.X) + un = findUndefinedGlobal(store, last, cx.X, nil) if un != "" { return } if cx.Low != nil { - un = findUndefined(store, last, cx.Low) + un = findUndefinedGlobal(store, last, cx.Low, nil) if un != "" { return } } if cx.High != nil { - un = findUndefined(store, last, cx.High) + un = findUndefinedGlobal(store, last, cx.High, nil) if un != "" { return } } if cx.Max != nil { - un = findUndefined(store, last, cx.Max) + un = findUndefinedGlobal(store, last, cx.Max, nil) if un != "" { return } } case *StarExpr: - return findUndefined(store, last, cx.X) + return findUndefinedGlobal(store, last, cx.X, nil) case *RefExpr: - return findUndefined(store, last, cx.X) + return findUndefinedGlobal(store, last, cx.X, nil) case *TypeAssertExpr: - un = findUndefined(store, last, cx.X) + un = findUndefinedGlobal(store, last, cx.X, nil) if un != "" { return } - return findUndefined(store, last, cx.Type) + return findUndefinedGlobal(store, last, cx.Type, nil) case *UnaryExpr: - return findUndefined(store, last, cx.X) + return findUndefinedGlobal(store, last, cx.X, nil) case *CompositeLitExpr: var ct Type if cx.Type == nil { @@ -3855,7 +4257,7 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { ct = t cx.Type = constType(cx, t) } else { - un = findUndefined(store, last, cx.Type) + un = findUndefinedGlobal(store, last, cx.Type, nil) if un != "" { return } @@ -3872,18 +4274,18 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { switch ct.Kind() { case ArrayKind, SliceKind, MapKind: for _, kvx := range cx.Elts { - un = findUndefined(store, last, kvx.Key) + un = findUndefinedGlobal(store, last, kvx.Key, nil) if un != "" { return } - un = findUndefined2(store, last, kvx.Value, ct.Elem()) + un = findUndefinedGlobal(store, last, kvx.Value, ct.Elem()) if un != "" { return } } case StructKind: for _, kvx := range cx.Elts { - un = findUndefined(store, last, kvx.Value) + un = findUndefinedGlobal(store, last, kvx.Value, nil) if un != "" { return } @@ -3894,43 +4296,260 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { ct.String())) } case *FuncLitExpr: - return findUndefined(store, last, &cx.Type) + for _, stmt := range cx.Body { + un = findUndefinedStmt(store, cx, stmt, t) + + if un != "" { + return + } + } + return findUndefinedGlobal(store, last, &cx.Type, nil) case *FieldTypeExpr: - return findUndefined(store, last, cx.Type) + return findUndefinedGlobal(store, last, cx.Type, nil) case *ArrayTypeExpr: if cx.Len != nil { - un = findUndefined(store, last, cx.Len) + un = findUndefinedGlobal(store, last, cx.Len, nil) if un != "" { return } } - return findUndefined(store, last, cx.Elt) + return findUndefinedGlobal(store, last, cx.Elt, nil) case *SliceTypeExpr: - return findUndefined(store, last, cx.Elt) + return findUndefinedGlobal(store, last, cx.Elt, nil) case *InterfaceTypeExpr: for i := range cx.Methods { - un = findUndefined(store, last, &cx.Methods[i]) + un = findUndefinedGlobal(store, last, &cx.Methods[i], nil) if un != "" { return } } case *ChanTypeExpr: - return findUndefined(store, last, cx.Value) + return findUndefinedGlobal(store, last, cx.Value, nil) case *FuncTypeExpr: for i := range cx.Params { - un = findUndefined(store, last, &cx.Params[i]) + un = findUndefinedGlobal(store, last, &cx.Params[i], nil) if un != "" { return } } for i := range cx.Results { - un = findUndefined(store, last, &cx.Results[i]) + un = findUndefinedGlobal(store, last, &cx.Results[i], nil) if un != "" { return } } case *MapTypeExpr: - un = findUndefined(store, last, cx.Key) + un = findUndefinedGlobal(store, last, cx.Key, nil) + if un != "" { + return + } + un = findUndefinedGlobal(store, last, cx.Value, nil) + if un != "" { + return + } + case *StructTypeExpr: + for i := range cx.Fields { + un = findUndefinedGlobal(store, last, &cx.Fields[i], nil) + if un != "" { + return + } + } + case *MaybeNativeTypeExpr: + un = findUndefinedGlobal(store, last, cx.Type, nil) + if un != "" { + return + } + case *CallExpr: + un = findUndefinedGlobal(store, last, cx.Func, nil) + if un != "" { + return + } + for i := range cx.Args { + un = findUndefinedGlobal(store, last, cx.Args[i], nil) + if un != "" { + return + } + } + case *IndexExpr: + un = findUndefinedGlobal(store, last, cx.X, nil) + if un != "" { + return + } + un = findUndefinedGlobal(store, last, cx.Index, nil) + if un != "" { + return + } + case *constTypeExpr: + return + case *ConstExpr: + return + default: + panic(fmt.Sprintf( + "unexpected expr: %v (%v)", + x, reflect.TypeOf(x))) + } + return +} + +func findUndefined2(store Store, last BlockNode, x Expr, t Type, skipPredefined bool) (un Name) { + if x == nil { + return + } + switch cx := x.(type) { + case *NameExpr: + if tv := last.GetValueRef(store, cx.Name, skipPredefined); tv != nil { + return + } + if _, ok := UverseNode().GetLocalIndex(cx.Name); ok { + // XXX NOTE even if the name is shadowed by a file + // level declaration, it is fine to return here as it + // will be predefined later. + return + } + return cx.Name + case *BasicLitExpr: + return + case *BinaryExpr: + un = findUndefined2(store, last, cx.Left, nil, skipPredefined) + if un != "" { + return + } + un = findUndefined2(store, last, cx.Right, nil, skipPredefined) + if un != "" { + return + } + case *SelectorExpr: + return findUndefined2(store, last, cx.X, nil, skipPredefined) + case *SliceExpr: + un = findUndefined2(store, last, cx.X, nil, skipPredefined) + if un != "" { + return + } + if cx.Low != nil { + un = findUndefined2(store, last, cx.Low, nil, skipPredefined) + if un != "" { + return + } + } + if cx.High != nil { + un = findUndefined2(store, last, cx.High, nil, skipPredefined) + if un != "" { + return + } + } + if cx.Max != nil { + un = findUndefined2(store, last, cx.Max, nil, skipPredefined) + if un != "" { + return + } + } + case *StarExpr: + return findUndefined2(store, last, cx.X, nil, skipPredefined) + case *RefExpr: + return findUndefined2(store, last, cx.X, nil, skipPredefined) + case *TypeAssertExpr: + un = findUndefined2(store, last, cx.X, nil, skipPredefined) + if un != "" { + return + } + return findUndefined2(store, last, cx.Type, nil, skipPredefined) + case *UnaryExpr: + return findUndefined2(store, last, cx.X, nil, skipPredefined) + case *CompositeLitExpr: + var ct Type + if cx.Type == nil { + if t == nil { + panic("cannot elide unknown composite type") + } + ct = t + cx.Type = constType(cx, t) + } else { + un = findUndefined2(store, last, cx.Type, nil, skipPredefined) + if un != "" { + return + } + // preprocess now for eliding purposes. + // TODO recursive preprocessing here is hacky, find a better + // way. This cannot be done asynchronously, cuz undefined + // names ought to be returned immediately to let the caller + // predefine it. + cx.Type = Preprocess(store, last, cx.Type).(Expr) // recursive + ct = evalStaticType(store, last, cx.Type) + // elide composite lit element (nested) composite types. + elideCompositeElements(cx, ct) + } + switch ct.Kind() { + case ArrayKind, SliceKind, MapKind: + for _, kvx := range cx.Elts { + un = findUndefined2(store, last, kvx.Key, nil, skipPredefined) + if un != "" { + return + } + un = findUndefined2(store, last, kvx.Value, ct.Elem(), skipPredefined) + if un != "" { + return + } + } + case StructKind: + for _, kvx := range cx.Elts { + un = findUndefined2(store, last, kvx.Value, nil, skipPredefined) + if un != "" { + return + } + } + default: + panic(fmt.Sprintf( + "unexpected composite lit type %s", + ct.String())) + } + case *FuncLitExpr: + if cx.GetAttribute(ATTR_GLOBAL) == true { + for _, stmt := range cx.Body { + un = findUndefinedStmt(store, cx, stmt, t) + + if un != "" { + return + } + } + } + + return findUndefined2(store, last, &cx.Type, nil, skipPredefined) + case *FieldTypeExpr: + return findUndefined2(store, last, cx.Type, nil, skipPredefined) + case *ArrayTypeExpr: + if cx.Len != nil { + un = findUndefined2(store, last, cx.Len, nil, skipPredefined) + if un != "" { + return + } + } + return findUndefined2(store, last, cx.Elt, nil, skipPredefined) + case *SliceTypeExpr: + return findUndefined2(store, last, cx.Elt, nil, skipPredefined) + case *InterfaceTypeExpr: + for i := range cx.Methods { + un = findUndefined2(store, last, &cx.Methods[i], nil, skipPredefined) + if un != "" { + return + } + } + case *ChanTypeExpr: + return findUndefined2(store, last, cx.Value, nil, skipPredefined) + case *FuncTypeExpr: + for i := range cx.Params { + un = findUndefined2(store, last, &cx.Params[i], nil, skipPredefined) + if un != "" { + return + } + } + for i := range cx.Results { + un = findUndefined2(store, last, &cx.Results[i], nil, skipPredefined) + if un != "" { + return + } + } + case *MapTypeExpr: + un = findUndefined2(store, last, cx.Key, nil, skipPredefined) if un != "" { return } @@ -3940,33 +4559,34 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { } case *StructTypeExpr: for i := range cx.Fields { - un = findUndefined(store, last, &cx.Fields[i]) + un = findUndefined2(store, last, &cx.Fields[i], nil, skipPredefined) if un != "" { return } } case *MaybeNativeTypeExpr: - un = findUndefined(store, last, cx.Type) + un = findUndefined2(store, last, cx.Type, nil, skipPredefined) if un != "" { return } case *CallExpr: - un = findUndefined(store, last, cx.Func) + cx.Func.SetAttribute(ATTR_GLOBAL, cx.GetAttribute(ATTR_GLOBAL)) + un = findUndefined2(store, last, cx.Func, nil, skipPredefined) if un != "" { return } for i := range cx.Args { - un = findUndefined(store, last, cx.Args[i]) + un = findUndefined2(store, last, cx.Args[i], nil, skipPredefined) if un != "" { return } } case *IndexExpr: - un = findUndefined(store, last, cx.X) + un = findUndefined2(store, last, cx.X, nil, skipPredefined) if un != "" { return } - un = findUndefined(store, last, cx.Index) + un = findUndefined2(store, last, cx.Index, nil, skipPredefined) if un != "" { return } @@ -4068,7 +4688,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo // recursively predefine dependencies. for { - un := tryPredefine(store, last, d) + un := tryPredefine(store, pkg, last, d) if un != "" { // check circularity. for _, n := range *stack { @@ -4082,8 +4702,12 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo if !file.IsInitialized() { panic("all types from files in file-set should have already been predefined") } + + declaration := *decl + declaration.SetAttribute(ATTR_GLOBAL, true) + // predefine dependency (recursive). - *decl, _ = predefineNow2(store, file, *decl, stack) + *decl, _ = predefineNow2(store, file, declaration, stack) } else { break } @@ -4191,7 +4815,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo // side effects. This function works for all block nodes and // must be called for name declarations within (non-file, // non-package) stmt bodies. -func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { +func tryPredefine(store Store, pkg *PackageNode, last BlockNode, d Decl) (un Name) { if d.GetAttribute(ATTR_PREDEFINED) == true { panic(fmt.Sprintf("decl node already predefined! %v", d)) } @@ -4207,6 +4831,18 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { // so value paths cannot be used here. switch d := d.(type) { case *ImportDecl: + // stdlib internal package + if strings.HasPrefix(d.PkgPath, "internal/") && !IsStdlib(pkg.PkgPath) { + panic("cannot import stdlib internal/ package outside of standard library") + } + + base, isInternal := IsInternalPath(d.PkgPath) + if isInternal && + pkg.PkgPath != base && + !strings.HasPrefix(pkg.PkgPath, base+"/") { + panic("internal/ packages can only be imported by packages rooted at the parent of \"internal\"") + } + pv := store.GetPackage(d.PkgPath, true) if pv == nil { panic(fmt.Sprintf( @@ -4241,6 +4877,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { return } for _, vx := range d.Values { + vx.SetAttribute(ATTR_GLOBAL, d.GetAttribute(ATTR_GLOBAL)) un = findUndefined(store, last, vx) if un != "" { return @@ -4369,7 +5006,6 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } // define package-level function. ft := &FuncType{} - pkg := skipFile(last).(*PackageNode) // define a FuncValue w/ above type as d.Name. // fill in later during *FuncDecl:BLOCK. // The body may get altered during preprocessing later. @@ -4412,7 +5048,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { return "" } -func constInt(source Expr, i int) *ConstExpr { +func constInt(source Expr, i int64) *ConstExpr { cx := &ConstExpr{Source: source} cx.T = IntType cx.SetInt(i) @@ -4468,7 +5104,8 @@ func fillNameExprPath(last BlockNode, nx *NameExpr, isDefineLHS bool) { break } } - path.Depth += uint8(i) + path.SetDepth(path.Depth + uint8(i)) + path.Validate() nx.Path = path return } diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 509fcd67a60..a300e3425e7 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -321,24 +321,12 @@ func (rlm *Realm) MarkNewEscaped(oo Object) { // transactions // OpReturn calls this when exiting a realm transaction. -func (rlm *Realm) FinalizeRealmTransaction(readonly bool, store Store) { +func (rlm *Realm) FinalizeRealmTransaction(store Store) { if bm.OpsEnabled { bm.PauseOpCode() defer bm.ResumeOpCode() } - if readonly { - if true || - len(rlm.newCreated) > 0 || - len(rlm.newEscaped) > 0 || - len(rlm.newDeleted) > 0 || - len(rlm.created) > 0 || - len(rlm.updated) > 0 || - len(rlm.deleted) > 0 || - len(rlm.escaped) > 0 { - panic("realm updates in readonly transaction") - } - return - } + if debug { // * newCreated - may become created unless ancestor is deleted // * newDeleted - may become deleted unless attached to new-real owner diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 3a70d07381b..1ddc97ed0d0 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -246,14 +246,12 @@ func CopyFromCachedStore(destStore, cachedStore Store, cachedBase, cachedIavl st ds.iavlStore.Set(iter.Key(), iter.Value()) } - ss.cacheTypes.Iterate()(func(k TypeID, v Type) bool { + for k, v := range ss.cacheTypes.Iterate() { ds.cacheTypes.Set(k, v) - return true - }) - ss.cacheNodes.Iterate()(func(k Location, v BlockNode) bool { + } + for k, v := range ss.cacheNodes.Iterate() { ds.cacheNodes.Set(k, v) - return true - }) + } } func (ds *defaultStore) GetAllocator() *Allocator { @@ -1028,7 +1026,7 @@ func InitStoreCaches(store Store) { StringType, UntypedStringType, IntType, Int8Type, Int16Type, Int32Type, Int64Type, UntypedRuneType, UintType, Uint8Type, Uint16Type, Uint32Type, Uint64Type, - BigintType, UntypedBigintType, + UntypedBigintType, gTypeType, gPackageType, blockType{}, diff --git a/gnovm/pkg/gnolang/string_methods.go b/gnovm/pkg/gnolang/string_methods.go new file mode 100644 index 00000000000..565b5860708 --- /dev/null +++ b/gnovm/pkg/gnolang/string_methods.go @@ -0,0 +1,466 @@ +// Code generated by "stringer -type=Kind,Op,TransCtrl,TransField,VPType,Word -output string_methods.go ."; DO NOT EDIT. + +package gnolang + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[InvalidKind-0] + _ = x[BoolKind-1] + _ = x[StringKind-2] + _ = x[IntKind-3] + _ = x[Int8Kind-4] + _ = x[Int16Kind-5] + _ = x[Int32Kind-6] + _ = x[Int64Kind-7] + _ = x[UintKind-8] + _ = x[Uint8Kind-9] + _ = x[Uint16Kind-10] + _ = x[Uint32Kind-11] + _ = x[Uint64Kind-12] + _ = x[Float32Kind-13] + _ = x[Float64Kind-14] + _ = x[BigintKind-15] + _ = x[BigdecKind-16] + _ = x[ArrayKind-17] + _ = x[SliceKind-18] + _ = x[PointerKind-19] + _ = x[StructKind-20] + _ = x[PackageKind-21] + _ = x[InterfaceKind-22] + _ = x[ChanKind-23] + _ = x[FuncKind-24] + _ = x[MapKind-25] + _ = x[TypeKind-26] + _ = x[BlockKind-27] + _ = x[HeapItemKind-28] + _ = x[TupleKind-29] + _ = x[RefTypeKind-30] +} + +const _Kind_name = "InvalidKindBoolKindStringKindIntKindInt8KindInt16KindInt32KindInt64KindUintKindUint8KindUint16KindUint32KindUint64KindFloat32KindFloat64KindBigintKindBigdecKindArrayKindSliceKindPointerKindStructKindPackageKindInterfaceKindChanKindFuncKindMapKindTypeKindBlockKindHeapItemKindTupleKindRefTypeKind" + +var _Kind_index = [...]uint16{0, 11, 19, 29, 36, 44, 53, 62, 71, 79, 88, 98, 108, 118, 129, 140, 150, 160, 169, 178, 189, 199, 210, 223, 231, 239, 246, 254, 263, 275, 284, 295} + +func (i Kind) String() string { + if i >= Kind(len(_Kind_index)-1) { + return "Kind(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Kind_name[_Kind_index[i]:_Kind_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[OpInvalid-0] + _ = x[OpHalt-1] + _ = x[OpNoop-2] + _ = x[OpExec-3] + _ = x[OpPrecall-4] + _ = x[OpCall-5] + _ = x[OpCallNativeBody-6] + _ = x[OpReturn-7] + _ = x[OpReturnFromBlock-8] + _ = x[OpReturnToBlock-9] + _ = x[OpDefer-10] + _ = x[OpCallDeferNativeBody-11] + _ = x[OpGo-12] + _ = x[OpSelect-13] + _ = x[OpSwitchClause-14] + _ = x[OpSwitchClauseCase-15] + _ = x[OpTypeSwitch-16] + _ = x[OpIfCond-17] + _ = x[OpPopValue-18] + _ = x[OpPopResults-19] + _ = x[OpPopBlock-20] + _ = x[OpPopFrameAndReset-21] + _ = x[OpPanic1-22] + _ = x[OpPanic2-23] + _ = x[OpUpos-32] + _ = x[OpUneg-33] + _ = x[OpUnot-34] + _ = x[OpUxor-35] + _ = x[OpUrecv-37] + _ = x[OpLor-38] + _ = x[OpLand-39] + _ = x[OpEql-40] + _ = x[OpNeq-41] + _ = x[OpLss-42] + _ = x[OpLeq-43] + _ = x[OpGtr-44] + _ = x[OpGeq-45] + _ = x[OpAdd-46] + _ = x[OpSub-47] + _ = x[OpBor-48] + _ = x[OpXor-49] + _ = x[OpMul-50] + _ = x[OpQuo-51] + _ = x[OpRem-52] + _ = x[OpShl-53] + _ = x[OpShr-54] + _ = x[OpBand-55] + _ = x[OpBandn-56] + _ = x[OpEval-64] + _ = x[OpBinary1-65] + _ = x[OpIndex1-66] + _ = x[OpIndex2-67] + _ = x[OpSelector-68] + _ = x[OpSlice-69] + _ = x[OpStar-70] + _ = x[OpRef-71] + _ = x[OpTypeAssert1-72] + _ = x[OpTypeAssert2-73] + _ = x[OpStaticTypeOf-74] + _ = x[OpCompositeLit-75] + _ = x[OpArrayLit-76] + _ = x[OpSliceLit-77] + _ = x[OpSliceLit2-78] + _ = x[OpMapLit-79] + _ = x[OpStructLit-80] + _ = x[OpFuncLit-81] + _ = x[OpConvert-82] + _ = x[OpArrayLitGoNative-96] + _ = x[OpSliceLitGoNative-97] + _ = x[OpStructLitGoNative-98] + _ = x[OpCallGoNative-99] + _ = x[OpFieldType-112] + _ = x[OpArrayType-113] + _ = x[OpSliceType-114] + _ = x[OpPointerType-115] + _ = x[OpInterfaceType-116] + _ = x[OpChanType-117] + _ = x[OpFuncType-118] + _ = x[OpMapType-119] + _ = x[OpStructType-120] + _ = x[OpMaybeNativeType-121] + _ = x[OpAssign-128] + _ = x[OpAddAssign-129] + _ = x[OpSubAssign-130] + _ = x[OpMulAssign-131] + _ = x[OpQuoAssign-132] + _ = x[OpRemAssign-133] + _ = x[OpBandAssign-134] + _ = x[OpBandnAssign-135] + _ = x[OpBorAssign-136] + _ = x[OpXorAssign-137] + _ = x[OpShlAssign-138] + _ = x[OpShrAssign-139] + _ = x[OpDefine-140] + _ = x[OpInc-141] + _ = x[OpDec-142] + _ = x[OpValueDecl-144] + _ = x[OpTypeDecl-145] + _ = x[OpSticky-208] + _ = x[OpBody-209] + _ = x[OpForLoop-210] + _ = x[OpRangeIter-211] + _ = x[OpRangeIterString-212] + _ = x[OpRangeIterMap-213] + _ = x[OpRangeIterArrayPtr-214] + _ = x[OpReturnCallDefers-215] + _ = x[OpVoid-255] +} + +const ( + _Op_name_0 = "OpInvalidOpHaltOpNoopOpExecOpPrecallOpCallOpCallNativeBodyOpReturnOpReturnFromBlockOpReturnToBlockOpDeferOpCallDeferNativeBodyOpGoOpSelectOpSwitchClauseOpSwitchClauseCaseOpTypeSwitchOpIfCondOpPopValueOpPopResultsOpPopBlockOpPopFrameAndResetOpPanic1OpPanic2" + _Op_name_1 = "OpUposOpUnegOpUnotOpUxor" + _Op_name_2 = "OpUrecvOpLorOpLandOpEqlOpNeqOpLssOpLeqOpGtrOpGeqOpAddOpSubOpBorOpXorOpMulOpQuoOpRemOpShlOpShrOpBandOpBandn" + _Op_name_3 = "OpEvalOpBinary1OpIndex1OpIndex2OpSelectorOpSliceOpStarOpRefOpTypeAssert1OpTypeAssert2OpStaticTypeOfOpCompositeLitOpArrayLitOpSliceLitOpSliceLit2OpMapLitOpStructLitOpFuncLitOpConvert" + _Op_name_4 = "OpArrayLitGoNativeOpSliceLitGoNativeOpStructLitGoNativeOpCallGoNative" + _Op_name_5 = "OpFieldTypeOpArrayTypeOpSliceTypeOpPointerTypeOpInterfaceTypeOpChanTypeOpFuncTypeOpMapTypeOpStructTypeOpMaybeNativeType" + _Op_name_6 = "OpAssignOpAddAssignOpSubAssignOpMulAssignOpQuoAssignOpRemAssignOpBandAssignOpBandnAssignOpBorAssignOpXorAssignOpShlAssignOpShrAssignOpDefineOpIncOpDec" + _Op_name_7 = "OpValueDeclOpTypeDecl" + _Op_name_8 = "OpStickyOpBodyOpForLoopOpRangeIterOpRangeIterStringOpRangeIterMapOpRangeIterArrayPtrOpReturnCallDefers" + _Op_name_9 = "OpVoid" +) + +var ( + _Op_index_0 = [...]uint16{0, 9, 15, 21, 27, 36, 42, 58, 66, 83, 98, 105, 126, 130, 138, 152, 170, 182, 190, 200, 212, 222, 240, 248, 256} + _Op_index_1 = [...]uint8{0, 6, 12, 18, 24} + _Op_index_2 = [...]uint8{0, 7, 12, 18, 23, 28, 33, 38, 43, 48, 53, 58, 63, 68, 73, 78, 83, 88, 93, 99, 106} + _Op_index_3 = [...]uint8{0, 6, 15, 23, 31, 41, 48, 54, 59, 72, 85, 99, 113, 123, 133, 144, 152, 163, 172, 181} + _Op_index_4 = [...]uint8{0, 18, 36, 55, 69} + _Op_index_5 = [...]uint8{0, 11, 22, 33, 46, 61, 71, 81, 90, 102, 119} + _Op_index_6 = [...]uint8{0, 8, 19, 30, 41, 52, 63, 75, 88, 99, 110, 121, 132, 140, 145, 150} + _Op_index_7 = [...]uint8{0, 11, 21} + _Op_index_8 = [...]uint8{0, 8, 14, 23, 34, 51, 65, 84, 102} +) + +func (i Op) String() string { + switch { + case i <= 23: + return _Op_name_0[_Op_index_0[i]:_Op_index_0[i+1]] + case 32 <= i && i <= 35: + i -= 32 + return _Op_name_1[_Op_index_1[i]:_Op_index_1[i+1]] + case 37 <= i && i <= 56: + i -= 37 + return _Op_name_2[_Op_index_2[i]:_Op_index_2[i+1]] + case 64 <= i && i <= 82: + i -= 64 + return _Op_name_3[_Op_index_3[i]:_Op_index_3[i+1]] + case 96 <= i && i <= 99: + i -= 96 + return _Op_name_4[_Op_index_4[i]:_Op_index_4[i+1]] + case 112 <= i && i <= 121: + i -= 112 + return _Op_name_5[_Op_index_5[i]:_Op_index_5[i+1]] + case 128 <= i && i <= 142: + i -= 128 + return _Op_name_6[_Op_index_6[i]:_Op_index_6[i+1]] + case 144 <= i && i <= 145: + i -= 144 + return _Op_name_7[_Op_index_7[i]:_Op_index_7[i+1]] + case 208 <= i && i <= 215: + i -= 208 + return _Op_name_8[_Op_index_8[i]:_Op_index_8[i+1]] + case i == 255: + return _Op_name_9 + default: + return "Op(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[TRANS_CONTINUE-0] + _ = x[TRANS_SKIP-1] + _ = x[TRANS_EXIT-2] +} + +const _TransCtrl_name = "TRANS_CONTINUETRANS_SKIPTRANS_EXIT" + +var _TransCtrl_index = [...]uint8{0, 14, 24, 34} + +func (i TransCtrl) String() string { + if i >= TransCtrl(len(_TransCtrl_index)-1) { + return "TransCtrl(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _TransCtrl_name[_TransCtrl_index[i]:_TransCtrl_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[TRANS_ROOT-0] + _ = x[TRANS_BINARY_LEFT-1] + _ = x[TRANS_BINARY_RIGHT-2] + _ = x[TRANS_CALL_FUNC-3] + _ = x[TRANS_CALL_ARG-4] + _ = x[TRANS_INDEX_X-5] + _ = x[TRANS_INDEX_INDEX-6] + _ = x[TRANS_SELECTOR_X-7] + _ = x[TRANS_SLICE_X-8] + _ = x[TRANS_SLICE_LOW-9] + _ = x[TRANS_SLICE_HIGH-10] + _ = x[TRANS_SLICE_MAX-11] + _ = x[TRANS_STAR_X-12] + _ = x[TRANS_REF_X-13] + _ = x[TRANS_TYPEASSERT_X-14] + _ = x[TRANS_TYPEASSERT_TYPE-15] + _ = x[TRANS_UNARY_X-16] + _ = x[TRANS_COMPOSITE_TYPE-17] + _ = x[TRANS_COMPOSITE_KEY-18] + _ = x[TRANS_COMPOSITE_VALUE-19] + _ = x[TRANS_FUNCLIT_TYPE-20] + _ = x[TRANS_FUNCLIT_HEAP_CAPTURE-21] + _ = x[TRANS_FUNCLIT_BODY-22] + _ = x[TRANS_FIELDTYPE_TYPE-23] + _ = x[TRANS_FIELDTYPE_TAG-24] + _ = x[TRANS_ARRAYTYPE_LEN-25] + _ = x[TRANS_ARRAYTYPE_ELT-26] + _ = x[TRANS_SLICETYPE_ELT-27] + _ = x[TRANS_INTERFACETYPE_METHOD-28] + _ = x[TRANS_CHANTYPE_VALUE-29] + _ = x[TRANS_FUNCTYPE_PARAM-30] + _ = x[TRANS_FUNCTYPE_RESULT-31] + _ = x[TRANS_MAPTYPE_KEY-32] + _ = x[TRANS_MAPTYPE_VALUE-33] + _ = x[TRANS_STRUCTTYPE_FIELD-34] + _ = x[TRANS_MAYBENATIVETYPE_TYPE-35] + _ = x[TRANS_ASSIGN_LHS-36] + _ = x[TRANS_ASSIGN_RHS-37] + _ = x[TRANS_BLOCK_BODY-38] + _ = x[TRANS_DECL_BODY-39] + _ = x[TRANS_DEFER_CALL-40] + _ = x[TRANS_EXPR_X-41] + _ = x[TRANS_FOR_INIT-42] + _ = x[TRANS_FOR_COND-43] + _ = x[TRANS_FOR_POST-44] + _ = x[TRANS_FOR_BODY-45] + _ = x[TRANS_GO_CALL-46] + _ = x[TRANS_IF_INIT-47] + _ = x[TRANS_IF_COND-48] + _ = x[TRANS_IF_BODY-49] + _ = x[TRANS_IF_ELSE-50] + _ = x[TRANS_IF_CASE_BODY-51] + _ = x[TRANS_INCDEC_X-52] + _ = x[TRANS_RANGE_X-53] + _ = x[TRANS_RANGE_KEY-54] + _ = x[TRANS_RANGE_VALUE-55] + _ = x[TRANS_RANGE_BODY-56] + _ = x[TRANS_RETURN_RESULT-57] + _ = x[TRANS_PANIC_EXCEPTION-58] + _ = x[TRANS_SELECT_CASE-59] + _ = x[TRANS_SELECTCASE_COMM-60] + _ = x[TRANS_SELECTCASE_BODY-61] + _ = x[TRANS_SEND_CHAN-62] + _ = x[TRANS_SEND_VALUE-63] + _ = x[TRANS_SWITCH_INIT-64] + _ = x[TRANS_SWITCH_X-65] + _ = x[TRANS_SWITCH_CASE-66] + _ = x[TRANS_SWITCHCASE_CASE-67] + _ = x[TRANS_SWITCHCASE_BODY-68] + _ = x[TRANS_FUNC_RECV-69] + _ = x[TRANS_FUNC_TYPE-70] + _ = x[TRANS_FUNC_BODY-71] + _ = x[TRANS_IMPORT_PATH-72] + _ = x[TRANS_CONST_TYPE-73] + _ = x[TRANS_CONST_VALUE-74] + _ = x[TRANS_VAR_NAME-75] + _ = x[TRANS_VAR_TYPE-76] + _ = x[TRANS_VAR_VALUE-77] + _ = x[TRANS_TYPE_TYPE-78] + _ = x[TRANS_FILE_BODY-79] +} + +const _TransField_name = "TRANS_ROOTTRANS_BINARY_LEFTTRANS_BINARY_RIGHTTRANS_CALL_FUNCTRANS_CALL_ARGTRANS_INDEX_XTRANS_INDEX_INDEXTRANS_SELECTOR_XTRANS_SLICE_XTRANS_SLICE_LOWTRANS_SLICE_HIGHTRANS_SLICE_MAXTRANS_STAR_XTRANS_REF_XTRANS_TYPEASSERT_XTRANS_TYPEASSERT_TYPETRANS_UNARY_XTRANS_COMPOSITE_TYPETRANS_COMPOSITE_KEYTRANS_COMPOSITE_VALUETRANS_FUNCLIT_TYPETRANS_FUNCLIT_HEAP_CAPTURETRANS_FUNCLIT_BODYTRANS_FIELDTYPE_TYPETRANS_FIELDTYPE_TAGTRANS_ARRAYTYPE_LENTRANS_ARRAYTYPE_ELTTRANS_SLICETYPE_ELTTRANS_INTERFACETYPE_METHODTRANS_CHANTYPE_VALUETRANS_FUNCTYPE_PARAMTRANS_FUNCTYPE_RESULTTRANS_MAPTYPE_KEYTRANS_MAPTYPE_VALUETRANS_STRUCTTYPE_FIELDTRANS_MAYBENATIVETYPE_TYPETRANS_ASSIGN_LHSTRANS_ASSIGN_RHSTRANS_BLOCK_BODYTRANS_DECL_BODYTRANS_DEFER_CALLTRANS_EXPR_XTRANS_FOR_INITTRANS_FOR_CONDTRANS_FOR_POSTTRANS_FOR_BODYTRANS_GO_CALLTRANS_IF_INITTRANS_IF_CONDTRANS_IF_BODYTRANS_IF_ELSETRANS_IF_CASE_BODYTRANS_INCDEC_XTRANS_RANGE_XTRANS_RANGE_KEYTRANS_RANGE_VALUETRANS_RANGE_BODYTRANS_RETURN_RESULTTRANS_PANIC_EXCEPTIONTRANS_SELECT_CASETRANS_SELECTCASE_COMMTRANS_SELECTCASE_BODYTRANS_SEND_CHANTRANS_SEND_VALUETRANS_SWITCH_INITTRANS_SWITCH_XTRANS_SWITCH_CASETRANS_SWITCHCASE_CASETRANS_SWITCHCASE_BODYTRANS_FUNC_RECVTRANS_FUNC_TYPETRANS_FUNC_BODYTRANS_IMPORT_PATHTRANS_CONST_TYPETRANS_CONST_VALUETRANS_VAR_NAMETRANS_VAR_TYPETRANS_VAR_VALUETRANS_TYPE_TYPETRANS_FILE_BODY" + +var _TransField_index = [...]uint16{0, 10, 27, 45, 60, 74, 87, 104, 120, 133, 148, 164, 179, 191, 202, 220, 241, 254, 274, 293, 314, 332, 358, 376, 396, 415, 434, 453, 472, 498, 518, 538, 559, 576, 595, 617, 643, 659, 675, 691, 706, 722, 734, 748, 762, 776, 790, 803, 816, 829, 842, 855, 873, 887, 900, 915, 932, 948, 967, 988, 1005, 1026, 1047, 1062, 1078, 1095, 1109, 1126, 1147, 1168, 1183, 1198, 1213, 1230, 1246, 1263, 1277, 1291, 1306, 1321, 1336} + +func (i TransField) String() string { + if i >= TransField(len(_TransField_index)-1) { + return "TransField(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _TransField_name[_TransField_index[i]:_TransField_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[VPUverse-0] + _ = x[VPBlock-1] + _ = x[VPField-2] + _ = x[VPValMethod-3] + _ = x[VPPtrMethod-4] + _ = x[VPInterface-5] + _ = x[VPSubrefField-6] + _ = x[VPDerefField-18] + _ = x[VPDerefValMethod-19] + _ = x[VPDerefPtrMethod-20] + _ = x[VPDerefInterface-21] + _ = x[VPNative-32] +} + +const ( + _VPType_name_0 = "VPUverseVPBlockVPFieldVPValMethodVPPtrMethodVPInterfaceVPSubrefField" + _VPType_name_1 = "VPDerefFieldVPDerefValMethodVPDerefPtrMethodVPDerefInterface" + _VPType_name_2 = "VPNative" +) + +var ( + _VPType_index_0 = [...]uint8{0, 8, 15, 22, 33, 44, 55, 68} + _VPType_index_1 = [...]uint8{0, 12, 28, 44, 60} +) + +func (i VPType) String() string { + switch { + case i <= 6: + return _VPType_name_0[_VPType_index_0[i]:_VPType_index_0[i+1]] + case 18 <= i && i <= 21: + i -= 18 + return _VPType_name_1[_VPType_index_1[i]:_VPType_index_1[i+1]] + case i == 32: + return _VPType_name_2 + default: + return "VPType(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ILLEGAL-0] + _ = x[NAME-1] + _ = x[INT-2] + _ = x[FLOAT-3] + _ = x[IMAG-4] + _ = x[CHAR-5] + _ = x[STRING-6] + _ = x[ADD-7] + _ = x[SUB-8] + _ = x[MUL-9] + _ = x[QUO-10] + _ = x[REM-11] + _ = x[BAND-12] + _ = x[BOR-13] + _ = x[XOR-14] + _ = x[SHL-15] + _ = x[SHR-16] + _ = x[BAND_NOT-17] + _ = x[ADD_ASSIGN-18] + _ = x[SUB_ASSIGN-19] + _ = x[MUL_ASSIGN-20] + _ = x[QUO_ASSIGN-21] + _ = x[REM_ASSIGN-22] + _ = x[BAND_ASSIGN-23] + _ = x[BOR_ASSIGN-24] + _ = x[XOR_ASSIGN-25] + _ = x[SHL_ASSIGN-26] + _ = x[SHR_ASSIGN-27] + _ = x[BAND_NOT_ASSIGN-28] + _ = x[LAND-29] + _ = x[LOR-30] + _ = x[ARROW-31] + _ = x[INC-32] + _ = x[DEC-33] + _ = x[EQL-34] + _ = x[LSS-35] + _ = x[GTR-36] + _ = x[ASSIGN-37] + _ = x[NOT-38] + _ = x[NEQ-39] + _ = x[LEQ-40] + _ = x[GEQ-41] + _ = x[DEFINE-42] + _ = x[BREAK-43] + _ = x[CASE-44] + _ = x[CHAN-45] + _ = x[CONST-46] + _ = x[CONTINUE-47] + _ = x[DEFAULT-48] + _ = x[DEFER-49] + _ = x[ELSE-50] + _ = x[FALLTHROUGH-51] + _ = x[FOR-52] + _ = x[FUNC-53] + _ = x[GO-54] + _ = x[GOTO-55] + _ = x[IF-56] + _ = x[IMPORT-57] + _ = x[INTERFACE-58] + _ = x[MAP-59] + _ = x[PACKAGE-60] + _ = x[RANGE-61] + _ = x[RETURN-62] + _ = x[SELECT-63] + _ = x[STRUCT-64] + _ = x[SWITCH-65] + _ = x[TYPE-66] + _ = x[VAR-67] +} + +const _Word_name = "ILLEGALNAMEINTFLOATIMAGCHARSTRINGADDSUBMULQUOREMBANDBORXORSHLSHRBAND_NOTADD_ASSIGNSUB_ASSIGNMUL_ASSIGNQUO_ASSIGNREM_ASSIGNBAND_ASSIGNBOR_ASSIGNXOR_ASSIGNSHL_ASSIGNSHR_ASSIGNBAND_NOT_ASSIGNLANDLORARROWINCDECEQLLSSGTRASSIGNNOTNEQLEQGEQDEFINEBREAKCASECHANCONSTCONTINUEDEFAULTDEFERELSEFALLTHROUGHFORFUNCGOGOTOIFIMPORTINTERFACEMAPPACKAGERANGERETURNSELECTSTRUCTSWITCHTYPEVAR" + +var _Word_index = [...]uint16{0, 7, 11, 14, 19, 23, 27, 33, 36, 39, 42, 45, 48, 52, 55, 58, 61, 64, 72, 82, 92, 102, 112, 122, 133, 143, 153, 163, 173, 188, 192, 195, 200, 203, 206, 209, 212, 215, 221, 224, 227, 230, 233, 239, 244, 248, 252, 257, 265, 272, 277, 281, 292, 295, 299, 301, 305, 307, 313, 322, 325, 332, 337, 343, 349, 355, 361, 365, 368} + +func (i Word) String() string { + if i < 0 || i >= Word(len(_Word_index)-1) { + return "Word(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Word_name[_Word_index[i]:_Word_index[i+1]] +} diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/a.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/a.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/b.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/b.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3013.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3013.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine2.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine2.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine3.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine3.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine4.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine4.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine5.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine5.go_fuzz diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine6.go_fuzz similarity index 100% rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine6.go_fuzz diff --git a/gnovm/pkg/gnolang/transcribe.go b/gnovm/pkg/gnolang/transcribe.go index dab539a8707..572810e9668 100644 --- a/gnovm/pkg/gnolang/transcribe.go +++ b/gnovm/pkg/gnolang/transcribe.go @@ -14,7 +14,6 @@ type ( const ( TRANS_CONTINUE TransCtrl = iota TRANS_SKIP - TRANS_BREAK TRANS_EXIT ) @@ -101,7 +100,7 @@ const ( TRANS_IMPORT_PATH TRANS_CONST_TYPE TRANS_CONST_VALUE - TRANS_VAR_NAME // XXX stringer + TRANS_VAR_NAME TRANS_VAR_TYPE TRANS_VAR_VALUE TRANS_TYPE_TYPE @@ -113,8 +112,6 @@ const ( // - TRANS_SKIP to break out of the // ENTER,CHILDS1,[BLOCK,CHILDS2]?,LEAVE sequence for that node, // i.e. skipping (the rest of) it; -// - TRANS_BREAK to break out of looping in CHILDS1 or CHILDS2, -// but still perform TRANS_LEAVE. // - TRANS_EXIT to stop traversing altogether. // // Do not mutate ns. @@ -168,9 +165,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Args { cnn.Args[idx] = transcribe(t, nns, TRANS_CALL_ARG, idx, cnn.Args[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -248,16 +243,12 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc k, v := kvx.Key, kvx.Value if k != nil { k = transcribe(t, nns, TRANS_COMPOSITE_KEY, idx, k, &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } v = transcribe(t, nns, TRANS_COMPOSITE_VALUE, idx, v, &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } cnn.Elts[idx] = KeyValueExpr{Key: k, Value: v} @@ -269,9 +260,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.HeapCaptures { cnn.HeapCaptures[idx] = *(transcribe(t, nns, TRANS_FUNCLIT_HEAP_CAPTURE, idx, &cnn.HeapCaptures[idx], &c).(*NameExpr)) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -285,9 +274,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_FUNCLIT_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -321,9 +308,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc case *InterfaceTypeExpr: for idx := range cnn.Methods { cnn.Methods[idx] = *transcribe(t, nns, TRANS_INTERFACETYPE_METHOD, idx, &cnn.Methods[idx], &c).(*FieldTypeExpr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -341,9 +326,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Results { cnn.Results[idx] = *transcribe(t, nns, TRANS_FUNCTYPE_RESULT, idx, &cnn.Results[idx], &c).(*FieldTypeExpr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -359,9 +342,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc case *StructTypeExpr: for idx := range cnn.Fields { cnn.Fields[idx] = *transcribe(t, nns, TRANS_STRUCTTYPE_FIELD, idx, &cnn.Fields[idx], &c).(*FieldTypeExpr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -373,17 +354,13 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc case *AssignStmt: for idx := range cnn.Lhs { cnn.Lhs[idx] = transcribe(t, nns, TRANS_ASSIGN_LHS, idx, cnn.Lhs[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } for idx := range cnn.Rhs { cnn.Rhs[idx] = transcribe(t, nns, TRANS_ASSIGN_RHS, idx, cnn.Rhs[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -398,9 +375,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_BLOCK_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -409,9 +384,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_DECL_BODY, idx, cnn.Body[idx], &c).(SimpleDeclStmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -455,9 +428,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_FOR_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -506,9 +477,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_IF_CASE_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -544,18 +513,14 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_RANGE_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } case *ReturnStmt: for idx := range cnn.Results { cnn.Results[idx] = transcribe(t, nns, TRANS_RETURN_RESULT, idx, cnn.Results[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -564,9 +529,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc case *SelectStmt: for idx := range cnn.Cases { cnn.Cases[idx] = *transcribe(t, nns, TRANS_SELECT_CASE, idx, &cnn.Cases[idx], &c).(*SelectCaseStmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -585,9 +548,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_SELECTCASE_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -632,9 +593,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Clauses { cnn.Clauses[idx] = *transcribe(t, nns, TRANS_SWITCH_CASE, idx, &cnn.Clauses[idx], &c).(*SwitchClauseStmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -652,18 +611,14 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Cases { cnn.Cases[idx] = transcribe(t, nns, TRANS_SWITCHCASE_CASE, idx, cnn.Cases[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_SWITCHCASE_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -688,9 +643,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc // iterate over Body; its length can change if a statement is decomposed. for idx := 0; idx < len(cnn.Body); idx++ { cnn.Body[idx] = transcribe(t, nns, TRANS_FUNC_BODY, idx, cnn.Body[idx], &c).(Stmt) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -709,9 +662,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Values { cnn.Values[idx] = transcribe(t, nns, TRANS_VAR_VALUE, idx, cnn.Values[idx], &c).(Expr) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -730,9 +681,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } for idx := range cnn.Decls { cnn.Decls[idx] = transcribe(t, nns, TRANS_FILE_BODY, idx, cnn.Decls[idx], &c).(Decl) - if isBreak(c) { - break - } else if isStopOrSkip(nc, c) { + if isStopOrSkip(nc, c) { return } } @@ -768,12 +717,3 @@ func isStopOrSkip(oldnc *TransCtrl, nc TransCtrl) (stop bool) { panic("should not happen") } } - -// returns true if transcribe() should break (a loop). -func isBreak(nc TransCtrl) (brek bool) { - if nc == TRANS_BREAK { - return true - } else { - return false - } -} diff --git a/gnovm/pkg/gnolang/transctrl_string.go b/gnovm/pkg/gnolang/transctrl_string.go deleted file mode 100644 index 92d33c65da5..00000000000 --- a/gnovm/pkg/gnolang/transctrl_string.go +++ /dev/null @@ -1,26 +0,0 @@ -// Code generated by "stringer -type=TransCtrl ./pkg/gnolang"; DO NOT EDIT. - -package gnolang - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[TRANS_CONTINUE-0] - _ = x[TRANS_SKIP-1] - _ = x[TRANS_BREAK-2] - _ = x[TRANS_EXIT-3] -} - -const _TransCtrl_name = "TRANS_CONTINUETRANS_SKIPTRANS_BREAKTRANS_EXIT" - -var _TransCtrl_index = [...]uint8{0, 14, 24, 35, 45} - -func (i TransCtrl) String() string { - if i >= TransCtrl(len(_TransCtrl_index)-1) { - return "TransCtrl(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _TransCtrl_name[_TransCtrl_index[i]:_TransCtrl_index[i+1]] -} diff --git a/gnovm/pkg/gnolang/transfield_string.go b/gnovm/pkg/gnolang/transfield_string.go deleted file mode 100644 index 31afcf2be0d..00000000000 --- a/gnovm/pkg/gnolang/transfield_string.go +++ /dev/null @@ -1,102 +0,0 @@ -// Code generated by "stringer -type=TransField ./pkg/gnolang"; DO NOT EDIT. - -package gnolang - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[TRANS_ROOT-0] - _ = x[TRANS_BINARY_LEFT-1] - _ = x[TRANS_BINARY_RIGHT-2] - _ = x[TRANS_CALL_FUNC-3] - _ = x[TRANS_CALL_ARG-4] - _ = x[TRANS_INDEX_X-5] - _ = x[TRANS_INDEX_INDEX-6] - _ = x[TRANS_SELECTOR_X-7] - _ = x[TRANS_SLICE_X-8] - _ = x[TRANS_SLICE_LOW-9] - _ = x[TRANS_SLICE_HIGH-10] - _ = x[TRANS_SLICE_MAX-11] - _ = x[TRANS_STAR_X-12] - _ = x[TRANS_REF_X-13] - _ = x[TRANS_TYPEASSERT_X-14] - _ = x[TRANS_TYPEASSERT_TYPE-15] - _ = x[TRANS_UNARY_X-16] - _ = x[TRANS_COMPOSITE_TYPE-17] - _ = x[TRANS_COMPOSITE_KEY-18] - _ = x[TRANS_COMPOSITE_VALUE-19] - _ = x[TRANS_FUNCLIT_TYPE-20] - _ = x[TRANS_FUNCLIT_HEAP_CAPTURE-21] - _ = x[TRANS_FUNCLIT_BODY-22] - _ = x[TRANS_FIELDTYPE_TYPE-23] - _ = x[TRANS_FIELDTYPE_TAG-24] - _ = x[TRANS_ARRAYTYPE_LEN-25] - _ = x[TRANS_ARRAYTYPE_ELT-26] - _ = x[TRANS_SLICETYPE_ELT-27] - _ = x[TRANS_INTERFACETYPE_METHOD-28] - _ = x[TRANS_CHANTYPE_VALUE-29] - _ = x[TRANS_FUNCTYPE_PARAM-30] - _ = x[TRANS_FUNCTYPE_RESULT-31] - _ = x[TRANS_MAPTYPE_KEY-32] - _ = x[TRANS_MAPTYPE_VALUE-33] - _ = x[TRANS_STRUCTTYPE_FIELD-34] - _ = x[TRANS_MAYBENATIVETYPE_TYPE-35] - _ = x[TRANS_ASSIGN_LHS-36] - _ = x[TRANS_ASSIGN_RHS-37] - _ = x[TRANS_BLOCK_BODY-38] - _ = x[TRANS_DECL_BODY-39] - _ = x[TRANS_DEFER_CALL-40] - _ = x[TRANS_EXPR_X-41] - _ = x[TRANS_FOR_INIT-42] - _ = x[TRANS_FOR_COND-43] - _ = x[TRANS_FOR_POST-44] - _ = x[TRANS_FOR_BODY-45] - _ = x[TRANS_GO_CALL-46] - _ = x[TRANS_IF_INIT-47] - _ = x[TRANS_IF_COND-48] - _ = x[TRANS_IF_BODY-49] - _ = x[TRANS_IF_ELSE-50] - _ = x[TRANS_IF_CASE_BODY-51] - _ = x[TRANS_INCDEC_X-52] - _ = x[TRANS_RANGE_X-53] - _ = x[TRANS_RANGE_KEY-54] - _ = x[TRANS_RANGE_VALUE-55] - _ = x[TRANS_RANGE_BODY-56] - _ = x[TRANS_RETURN_RESULT-57] - _ = x[TRANS_PANIC_EXCEPTION-58] - _ = x[TRANS_SELECT_CASE-59] - _ = x[TRANS_SELECTCASE_COMM-60] - _ = x[TRANS_SELECTCASE_BODY-61] - _ = x[TRANS_SEND_CHAN-62] - _ = x[TRANS_SEND_VALUE-63] - _ = x[TRANS_SWITCH_INIT-64] - _ = x[TRANS_SWITCH_X-65] - _ = x[TRANS_SWITCH_CASE-66] - _ = x[TRANS_SWITCHCASE_CASE-67] - _ = x[TRANS_SWITCHCASE_BODY-68] - _ = x[TRANS_FUNC_RECV-69] - _ = x[TRANS_FUNC_TYPE-70] - _ = x[TRANS_FUNC_BODY-71] - _ = x[TRANS_IMPORT_PATH-72] - _ = x[TRANS_CONST_TYPE-73] - _ = x[TRANS_CONST_VALUE-74] - _ = x[TRANS_VAR_NAME-75] - _ = x[TRANS_VAR_TYPE-76] - _ = x[TRANS_VAR_VALUE-77] - _ = x[TRANS_TYPE_TYPE-78] - _ = x[TRANS_FILE_BODY-79] -} - -const _TransField_name = "TRANS_ROOTTRANS_BINARY_LEFTTRANS_BINARY_RIGHTTRANS_CALL_FUNCTRANS_CALL_ARGTRANS_INDEX_XTRANS_INDEX_INDEXTRANS_SELECTOR_XTRANS_SLICE_XTRANS_SLICE_LOWTRANS_SLICE_HIGHTRANS_SLICE_MAXTRANS_STAR_XTRANS_REF_XTRANS_TYPEASSERT_XTRANS_TYPEASSERT_TYPETRANS_UNARY_XTRANS_COMPOSITE_TYPETRANS_COMPOSITE_KEYTRANS_COMPOSITE_VALUETRANS_FUNCLIT_TYPETRANS_FUNCLIT_HEAP_CAPTURETRANS_FUNCLIT_BODYTRANS_FIELDTYPE_TYPETRANS_FIELDTYPE_TAGTRANS_ARRAYTYPE_LENTRANS_ARRAYTYPE_ELTTRANS_SLICETYPE_ELTTRANS_INTERFACETYPE_METHODTRANS_CHANTYPE_VALUETRANS_FUNCTYPE_PARAMTRANS_FUNCTYPE_RESULTTRANS_MAPTYPE_KEYTRANS_MAPTYPE_VALUETRANS_STRUCTTYPE_FIELDTRANS_MAYBENATIVETYPE_TYPETRANS_ASSIGN_LHSTRANS_ASSIGN_RHSTRANS_BLOCK_BODYTRANS_DECL_BODYTRANS_DEFER_CALLTRANS_EXPR_XTRANS_FOR_INITTRANS_FOR_CONDTRANS_FOR_POSTTRANS_FOR_BODYTRANS_GO_CALLTRANS_IF_INITTRANS_IF_CONDTRANS_IF_BODYTRANS_IF_ELSETRANS_IF_CASE_BODYTRANS_INCDEC_XTRANS_RANGE_XTRANS_RANGE_KEYTRANS_RANGE_VALUETRANS_RANGE_BODYTRANS_RETURN_RESULTTRANS_PANIC_EXCEPTIONTRANS_SELECT_CASETRANS_SELECTCASE_COMMTRANS_SELECTCASE_BODYTRANS_SEND_CHANTRANS_SEND_VALUETRANS_SWITCH_INITTRANS_SWITCH_XTRANS_SWITCH_CASETRANS_SWITCHCASE_CASETRANS_SWITCHCASE_BODYTRANS_FUNC_RECVTRANS_FUNC_TYPETRANS_FUNC_BODYTRANS_IMPORT_PATHTRANS_CONST_TYPETRANS_CONST_VALUETRANS_VAR_NAMETRANS_VAR_TYPETRANS_VAR_VALUETRANS_TYPE_TYPETRANS_FILE_BODY" - -var _TransField_index = [...]uint16{0, 10, 27, 45, 60, 74, 87, 104, 120, 133, 148, 164, 179, 191, 202, 220, 241, 254, 274, 293, 314, 332, 358, 376, 396, 415, 434, 453, 472, 498, 518, 538, 559, 576, 595, 617, 643, 659, 675, 691, 706, 722, 734, 748, 762, 776, 790, 803, 816, 829, 842, 855, 873, 887, 900, 915, 932, 948, 967, 988, 1005, 1026, 1047, 1062, 1078, 1095, 1109, 1126, 1147, 1168, 1183, 1198, 1213, 1230, 1246, 1263, 1277, 1291, 1306, 1321, 1336} - -func (i TransField) String() string { - if i >= TransField(len(_TransField_index)-1) { - return "TransField(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _TransField_name[_TransField_index[i]:_TransField_index[i+1]] -} diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index f96cb71e4b6..a79e9c43ecc 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -270,7 +270,7 @@ Main: switch { case fv.Name == "len": at := evalStaticTypeOf(store, last, currExpr.Args[0]) - if _, ok := baseOf(at).(*ArrayType); ok { + if _, ok := unwrapPointerType(baseOf(at)).(*ArrayType); ok { // ok break Main } @@ -278,7 +278,7 @@ Main: break Main case fv.Name == "cap": at := evalStaticTypeOf(store, last, currExpr.Args[0]) - if _, ok := baseOf(at).(*ArrayType); ok { + if _, ok := unwrapPointerType(baseOf(at)).(*ArrayType); ok { // ok break Main } diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 18774fcc462..3b7aea80e3f 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -108,9 +108,7 @@ const ( Float32Type Float64Type UntypedBigintType - BigintType UntypedBigdecType - BigdecType // UintptrType ) @@ -153,20 +151,16 @@ func (pt PrimitiveType) Specificity() int { return 0 case Float64Type: return 0 - case BigintType: - return 1 - case BigdecType: - return 2 case UntypedBigdecType: - return 3 + return 1 case UntypedStringType: - return 4 + return 2 case UntypedBigintType: - return 4 + return 2 case UntypedRuneType: - return 5 + return 3 case UntypedBoolType: - return 6 + return 4 default: panic(fmt.Sprintf("unexpected primitive type %v", pt)) } @@ -204,9 +198,9 @@ func (pt PrimitiveType) Kind() Kind { return Float32Kind case Float64Type: return Float64Kind - case BigintType, UntypedBigintType: + case UntypedBigintType: return BigintKind - case BigdecType, UntypedBigdecType: + case UntypedBigdecType: return BigdecKind default: panic(fmt.Sprintf("unexpected primitive type %v", pt)) @@ -256,12 +250,8 @@ func (pt PrimitiveType) TypeID() TypeID { return typeid("float64") case UntypedBigintType: return typeid(" bigint") - case BigintType: - return typeid("bigint") case UntypedBigdecType: return typeid(" bigdec") - case BigdecType: - return typeid("bigdec") default: panic(fmt.Sprintf("unexpected primitive type %v", pt)) } @@ -309,12 +299,8 @@ func (pt PrimitiveType) String() string { return string("float64") case UntypedBigintType: return string(" bigint") - case BigintType: - return string("bigint") case UntypedBigdecType: return string(" bigdec") - case BigdecType: - return string("bigdec") default: panic(fmt.Sprintf("unexpected primitive type %d", pt)) } @@ -467,27 +453,29 @@ func (l FieldTypeList) HasUnexported() bool { } func (l FieldTypeList) String() string { - ll := len(l) - s := "" - for i, ft := range l { - s += string(ft.Name) + " " + ft.Type.TypeID().String() - if i != ll-1 { - s += ";" - } - } - return s + return l.string(true, "; ") } -func (l FieldTypeList) StringWithCommas() string { - ll := len(l) - s := "" +// StringForFunc returns a list of the fields, suitable for functions. +// Compared to [FieldTypeList.String], this method does not return field names, +// and separates the fields using ", ". +func (l FieldTypeList) StringForFunc() string { + return l.string(false, ", ") +} + +func (l FieldTypeList) string(withName bool, sep string) string { + var bld strings.Builder for i, ft := range l { - s += string(ft.Name) + " " + ft.Type.String() - if i != ll-1 { - s += "," + if i != 0 { + bld.WriteString(sep) } + if withName { + bld.WriteString(string(ft.Name)) + bld.WriteByte(' ') + } + bld.WriteString(ft.Type.String()) } - return s + return bld.String() } // Like TypeID() but without considering field names; @@ -698,7 +686,7 @@ func (pt *PointerType) FindEmbeddedFieldType(callerPath string, n Name, m map[Ty case 1: // *DeclaredType > *StructType.Field has depth 1 (& type VPField). // *PointerType > *DeclaredType > *StructType.Field has depth 2. - trail[0].Depth = 2 + trail[0].SetDepth(2) /* // If trail[-1].Type == VPPtrMethod, set VPDerefPtrMethod. if len(trail) > 1 && trail[1].Type == VPPtrMethod { @@ -951,7 +939,7 @@ func (it *InterfaceType) String() string { it.Generic, FieldTypeList(it.Methods).String()) } else { - return fmt.Sprintf("interface{%s}", + return fmt.Sprintf("interface {%s}", FieldTypeList(it.Methods).String()) } } @@ -1132,6 +1120,8 @@ type FuncType struct { typeid TypeID bound *FuncType + + IsClosure bool } // true for predefined func types that are not filled in yet. @@ -1317,9 +1307,18 @@ func (ft *FuncType) TypeID() TypeID { } func (ft *FuncType) String() string { - return fmt.Sprintf("func(%s)(%s)", - FieldTypeList(ft.Params).StringWithCommas(), - FieldTypeList(ft.Results).StringWithCommas()) + switch len(ft.Results) { + case 0: + return fmt.Sprintf("func(%s)", FieldTypeList(ft.Params).StringForFunc()) + case 1: + return fmt.Sprintf("func(%s) %s", + FieldTypeList(ft.Params).StringForFunc(), + ft.Results[0].Type.String()) + default: + return fmt.Sprintf("func(%s) (%s)", + FieldTypeList(ft.Params).StringForFunc(), + FieldTypeList(ft.Results).StringForFunc()) + } } func (ft *FuncType) Elem() Type { @@ -1467,6 +1466,13 @@ func baseOf(t Type) Type { } } +func unwrapPointerType(t Type) Type { + if pt, ok := t.(*PointerType); ok { + return pt.Elem() + } + return t +} + // NOTE: it may be faster to switch on baseOf(). func (dt *DeclaredType) Kind() Kind { return dt.Base.Kind() @@ -1593,7 +1599,7 @@ func (dt *DeclaredType) GetPathForName(n Name) ValuePath { } // Otherwise it is underlying. path := dt.Base.(ValuePather).GetPathForName(n) - path.Depth += 1 + path.SetDepth(path.Depth + 1) return path } @@ -1663,7 +1669,8 @@ func (dt *DeclaredType) FindEmbeddedFieldType(callerPath string, n Name, m map[T panic("should not happen") } } - trail[0].Depth += 1 + + trail[0].SetDepth(trail[0].Depth + 1) return trail, hasPtr, rcvr, ft, false default: panic("should not happen") @@ -2197,9 +2204,9 @@ func KindOf(t Type) Kind { return Float32Kind case Float64Type: return Float64Kind - case BigintType, UntypedBigintType: + case UntypedBigintType: return BigintKind - case BigdecType, UntypedBigdecType: + case UntypedBigdecType: return BigdecKind default: panic(fmt.Sprintf("unexpected primitive type %s", t.String())) @@ -2392,12 +2399,6 @@ func fillEmbeddedName(ft *FieldType) { ft.Name = Name("float32") case Float64Type: ft.Name = Name("float64") - case BigintType: - ft.Name = Name("bigint") - case BigdecType: - ft.Name = Name("bigdec") - default: - panic("should not happen") } case *NativeType: panic("native type cannot be embedded") diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 975038314ad..040aba52961 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -129,7 +129,6 @@ func makeUverseNode() { def("._", undefined) // special, path is zero. def("iota", undefined) // special def("nil", undefined) - def("bigint", asValue(BigintType)) def("bool", asValue(BoolType)) def("byte", asValue(Uint8Type)) def("float32", asValue(Float32Type)) @@ -146,22 +145,8 @@ func makeUverseNode() { def("uint16", asValue(Uint16Type)) def("uint32", asValue(Uint32Type)) def("uint64", asValue(Uint64Type)) - // NOTE on 'typeval': We can't call the type of a TypeValue a - // "type", even though we want to, because it conflicts with - // the pre-existing syntax for type-switching, `switch - // x.(type) {case SomeType:...}`, for if x.(type) were not a - // type-switch but a type-assertion, and the resulting value - // could be any type, such as an IntType; whereas as the .X of - // a SwitchStmt, the type of an IntType value is not IntType - // but always a TypeType (all types are of type TypeType). - // - // The ideal solution is to keep the syntax consistent for - // type-assertions, but for backwards compatibility, the - // keyword that represents the TypeType type is not "type" but - // "typeval". The value of a "typeval" value is represented - // by a TypeValue. - def("typeval", asValue(gTypeType)) def("error", asValue(gErrorType)) + def("any", asValue(&InterfaceType{})) // Values def("true", untypedBool(true)) @@ -637,13 +622,11 @@ func makeUverseNode() { T: IntType, V: nil, } - res0.SetInt(arg0.TV.GetCapacity()) + res0.SetInt(int64(arg0.TV.GetCapacity())) m.PushValue(res0) return }, ) - def("close", undefined) - def("complex", undefined) defNative("copy", Flds( // params "dst", GenT("X", nil), @@ -693,7 +676,7 @@ func makeUverseNode() { T: IntType, V: nil, } - res0.SetInt(minl) + res0.SetInt(int64(minl)) m.PushValue(res0) return case *SliceType: @@ -719,7 +702,7 @@ func makeUverseNode() { T: IntType, V: nil, } - res0.SetInt(minl) + res0.SetInt(int64(minl)) m.PushValue(res0) return case *NativeType: @@ -791,7 +774,7 @@ func makeUverseNode() { T: IntType, V: nil, } - res0.SetInt(arg0.TV.GetLength()) + res0.SetInt(int64(arg0.TV.GetLength())) m.PushValue(res0) return }, @@ -814,7 +797,7 @@ func makeUverseNode() { et := bt.Elem() if vargsl == 1 { lv := vargs.TV.GetPointerAtIndexInt(m.Store, 0).Deref() - li := lv.ConvertGetInt() + li := int(lv.ConvertGetInt()) if et.Kind() == Uint8Kind { arrayValue := m.Alloc.NewDataArray(li) m.PushValue(TypedValue{ @@ -840,9 +823,9 @@ func makeUverseNode() { } } else if vargsl == 2 { lv := vargs.TV.GetPointerAtIndexInt(m.Store, 0).Deref() - li := lv.ConvertGetInt() + li := int(lv.ConvertGetInt()) cv := vargs.TV.GetPointerAtIndexInt(m.Store, 1).Deref() - ci := cv.ConvertGetInt() + ci := int(cv.ConvertGetInt()) if ci < li { panic(&Exception{Value: typedString(`makeslice: cap out of range`)}) @@ -896,7 +879,7 @@ func makeUverseNode() { return } else if vargsl == 1 { lv := vargs.TV.GetPointerAtIndexInt(m.Store, 0).Deref() - li := lv.ConvertGetInt() + li := int(lv.ConvertGetInt()) m.PushValue(TypedValue{ T: tt, V: m.Alloc.NewMap(li), @@ -926,7 +909,7 @@ func makeUverseNode() { return } else if vargsl == 1 { sv := vargs.TV.GetPointerAtIndexInt(m.Store, 0).Deref() - si := sv.ConvertGetInt() + si := int(sv.ConvertGetInt()) m.PushValue(TypedValue{ T: tt, V: m.Alloc.NewNative( @@ -1026,34 +1009,12 @@ func makeUverseNode() { "exception", AnyT(), ), func(m *Machine) { - if len(m.Exceptions) == 0 { + exception := m.Recover() + if exception == nil { m.PushValue(TypedValue{}) - return - } - - // If the exception is out of scope, this recover can't help; return nil. - if m.PanicScope <= m.DeferPanicScope { - m.PushValue(TypedValue{}) - return + } else { + m.PushValue(exception.Value) } - - exception := &m.Exceptions[len(m.Exceptions)-1] - - // If the frame the exception occurred in is not popped, it's possible that - // the exception is still in scope and can be recovered. - if !exception.Frame.Popped { - // If the frame is not the current frame, the exception is not in scope; return nil. - // This retrieves the second most recent call frame because the first most recent - // is the call to recover itself. - if frame := m.LastCallFrame(2); frame == nil || (frame != nil && frame != exception.Frame) { - m.PushValue(TypedValue{}) - return - } - } - - m.PushValue(exception.Value) - // Recover complete; remove exceptions. - m.Exceptions = nil }, ) uverseValue = uverseNode.NewPackage() diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index da887764c8e..30b0655e67f 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -20,6 +20,18 @@ import ( type Value interface { assertValue() String() string // for debugging + + // DeepFill returns the same value, filled. + // + // NOTE NOT LAZY (and potentially expensive) + // DeepFill() is only used for synchronous recursive + // filling before running genstd generated native bindings + // which use Gno2GoValue(). All other filling functionality is + // lazy, so avoid using this, and keep the logic lazy. + // + // NOTE must use the return value since PointerValue isn't a pointer + // receiver, and RefValue returns another type entirely. + DeepFill(store Store) Value } // Fixed size primitive types are represented in TypedValue.N @@ -79,26 +91,26 @@ type BigintValue struct { V *big.Int } -func (bv BigintValue) MarshalAmino() (string, error) { - bz, err := bv.V.MarshalText() +func (biv BigintValue) MarshalAmino() (string, error) { + bz, err := biv.V.MarshalText() if err != nil { return "", err } return string(bz), nil } -func (bv *BigintValue) UnmarshalAmino(s string) error { +func (biv *BigintValue) UnmarshalAmino(s string) error { vv := big.NewInt(0) err := vv.UnmarshalText([]byte(s)) if err != nil { return err } - bv.V = vv + biv.V = vv return nil } -func (bv BigintValue) Copy(alloc *Allocator) BigintValue { - return BigintValue{V: big.NewInt(0).Set(bv.V)} +func (biv BigintValue) Copy(alloc *Allocator) BigintValue { + return BigintValue{V: big.NewInt(0).Set(biv.V)} } // ---------------------------------------- @@ -108,27 +120,27 @@ type BigdecValue struct { V *apd.Decimal } -func (bv BigdecValue) MarshalAmino() (string, error) { - bz, err := bv.V.MarshalText() +func (bdv BigdecValue) MarshalAmino() (string, error) { + bz, err := bdv.V.MarshalText() if err != nil { return "", err } return string(bz), nil } -func (bv *BigdecValue) UnmarshalAmino(s string) error { +func (bdv *BigdecValue) UnmarshalAmino(s string) error { vv := apd.New(0, 0) err := vv.UnmarshalText([]byte(s)) if err != nil { return err } - bv.V = vv + bdv.V = vv return nil } -func (bv BigdecValue) Copy(alloc *Allocator) BigdecValue { +func (bdv BigdecValue) Copy(alloc *Allocator) BigdecValue { cp := apd.New(0, 0) - _, err := apd.BaseContext.Add(cp, cp, bv.V) + _, err := apd.BaseContext.Add(cp, cp, bdv.V) if err != nil { panic("should not happen") } @@ -1116,7 +1128,7 @@ func (tv *TypedValue) PrimitiveBytes() (data []byte) { case UintType, Uint64Type: data = make([]byte, 8) binary.LittleEndian.PutUint64( - data, uint64(tv.GetUint())) + data, tv.GetUint()) return data case Float32Type: data = make([]byte, 4) @@ -1130,8 +1142,6 @@ func (tv *TypedValue) PrimitiveBytes() (data []byte) { binary.LittleEndian.PutUint64( data, u64) return data - case BigintType: - return tv.V.(BigintValue).V.Bytes() default: panic(fmt.Sprintf( "unexpected primitive value type: %s", @@ -1190,7 +1200,7 @@ func (tv *TypedValue) GetString() string { return string(tv.V.(StringValue)) } -func (tv *TypedValue) SetInt(n int) { +func (tv *TypedValue) SetInt(n int64) { if debug { if tv.T.Kind() != IntKind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1198,19 +1208,16 @@ func (tv *TypedValue) SetInt(n int) { tv.T.String())) } } - // XXX probably should be coerced into int64 for determinism. - // XXX otherwise, all nodes must run in 64bit. - // XXX alternatively, require 64bit. - *(*int)(unsafe.Pointer(&tv.N)) = n + *(*int64)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) ConvertGetInt() int { +func (tv *TypedValue) ConvertGetInt() int64 { var store Store = nil // not used ConvertTo(nilAllocator, store, tv, IntType, false) return tv.GetInt() } -func (tv *TypedValue) GetInt() int { +func (tv *TypedValue) GetInt() int64 { if debug { if tv.T != nil && tv.T.Kind() != IntKind { panic(fmt.Sprintf( @@ -1218,7 +1225,7 @@ func (tv *TypedValue) GetInt() int { tv.T.String())) } } - return *(*int)(unsafe.Pointer(&tv.N)) + return *(*int64)(unsafe.Pointer(&tv.N)) } func (tv *TypedValue) SetInt8(n int8) { @@ -1309,7 +1316,7 @@ func (tv *TypedValue) GetInt64() int64 { return *(*int64)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) SetUint(n uint) { +func (tv *TypedValue) SetUint(n uint64) { if debug { if tv.T.Kind() != UintKind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1317,10 +1324,10 @@ func (tv *TypedValue) SetUint(n uint) { tv.T.String())) } } - *(*uint)(unsafe.Pointer(&tv.N)) = n + *(*uint64)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) GetUint() uint { +func (tv *TypedValue) GetUint() uint64 { if debug { if tv.T != nil && tv.T.Kind() != UintKind { panic(fmt.Sprintf( @@ -1328,7 +1335,7 @@ func (tv *TypedValue) GetUint() uint { tv.T.String())) } } - return *(*uint)(unsafe.Pointer(&tv.N)) + return *(*uint64)(unsafe.Pointer(&tv.N)) } func (tv *TypedValue) SetUint8(n uint8) { @@ -1563,6 +1570,7 @@ func (tv *TypedValue) ComputeMapKey(store Store, omitType bool) MapKey { pbz := tv.PrimitiveBytes() bz = append(bz, pbz...) case *PointerType: + fillValueTV(store, tv) ptr := uintptr(unsafe.Pointer(tv.V.(PointerValue).TV)) bz = append(bz, uintptrToBytes(&ptr)...) case FieldType: @@ -1669,7 +1677,7 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val dtv = tv case 1: dtv = tv - path.Depth = 0 + path.SetDepth(0) default: panic("should not happen") } @@ -1681,15 +1689,15 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val case 1: dtv = tv.V.(PointerValue).TV isPtr = true - path.Depth = 0 + path.SetDepth(0) case 2: dtv = tv.V.(PointerValue).TV isPtr = true - path.Depth = 0 + path.SetDepth(0) case 3: dtv = tv.V.(PointerValue).TV isPtr = true - path.Depth = 0 + path.SetDepth(0) default: panic("should not happen") } @@ -1703,7 +1711,7 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val dtv = tv.V.(PointerValue).TV isPtr = true path.Type = VPField - path.Depth = 0 + path.SetDepth(0) case 2: if tv.V == nil { panic(&Exception{Value: typedString("nil pointer dereference")}) @@ -1711,16 +1719,19 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val dtv = tv.V.(PointerValue).TV isPtr = true path.Type = VPField - path.Depth = 0 + path.SetDepth(0) case 3: dtv = tv.V.(PointerValue).TV isPtr = true path.Type = VPField - path.Depth = 0 + path.SetDepth(0) default: panic("should not happen") } case VPDerefValMethod: + if tv.V == nil { + panic(&Exception{Value: typedString("nil pointer dereference")}) + } dtv2 := tv.V.(PointerValue).TV dtv = &TypedValue{ // In case method is called on converted type, like ((*othertype)x).Method(). T: tv.T.Elem(), @@ -1970,7 +1981,7 @@ func (tv *TypedValue) GetPointerToFromTV(alloc *Allocator, store Store, path Val // Convenience for GetPointerAtIndex(). Slow. func (tv *TypedValue) GetPointerAtIndexInt(store Store, ii int) PointerValue { iv := TypedValue{T: IntType} - iv.SetInt(ii) + iv.SetInt(int64(ii)) return tv.GetPointerAtIndex(nilAllocator, store, &iv) } @@ -1979,7 +1990,7 @@ func (tv *TypedValue) GetPointerAtIndex(alloc *Allocator, store Store, iv *Typed case PrimitiveType: if bt == StringType || bt == UntypedStringType { sv := tv.GetString() - ii := iv.ConvertGetInt() + ii := int(iv.ConvertGetInt()) bv := &TypedValue{ // heap alloc T: Uint8Type, } @@ -2002,14 +2013,14 @@ func (tv *TypedValue) GetPointerAtIndex(alloc *Allocator, store Store, iv *Typed tv.T.String())) case *ArrayType: av := tv.V.(*ArrayValue) - ii := iv.ConvertGetInt() + ii := int(iv.ConvertGetInt()) return av.GetPointerAtIndexInt2(store, ii, bt.Elt) case *SliceType: if tv.V == nil { panic("nil slice index (out of bounds)") } sv := tv.V.(*SliceValue) - ii := iv.ConvertGetInt() + ii := int(iv.ConvertGetInt()) return sv.GetPointerAtIndexInt2(store, ii, bt.Elt) case *MapType: if tv.V == nil { @@ -2031,7 +2042,7 @@ func (tv *TypedValue) GetPointerAtIndex(alloc *Allocator, store Store, iv *Typed rv := nv.Value switch rt.Kind() { case reflect.Array, reflect.Slice, reflect.String: - ii := iv.ConvertGetInt() + ii := int(iv.ConvertGetInt()) erv := rv.Index(ii) etv := go2GnoValue(alloc, erv) return PointerValue{ @@ -2094,6 +2105,8 @@ func (tv *TypedValue) GetLength() int { return bt.Len case *SliceType: return 0 + case *MapType: + return 0 case *PointerType: if at, ok := bt.Elt.(*ArrayType); ok { return at.Len @@ -2325,6 +2338,14 @@ func (tv *TypedValue) GetSlice2(alloc *Allocator, lowVal, highVal, maxVal int) T } } +// Convenience for Value.DeepFill. +// NOTE: NOT LAZY (and potentially expensive) +func (tv *TypedValue) DeepFill(store Store) { + if tv.V != nil { + tv.V = tv.V.DeepFill(store) + } +} + // ---------------------------------------- // Block // @@ -2653,7 +2674,7 @@ func defaultTypedValue(alloc *Allocator, t Type) TypedValue { func typedInt(i int) TypedValue { tv := TypedValue{T: IntType} - tv.SetInt(i) + tv.SetInt(int64(i)) return tv } diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index e1c9378fe67..b8ab81dbde9 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -75,6 +75,11 @@ func ConvertTo(alloc *Allocator, store Store, tv *TypedValue, t Type, isConst bo GNO_CASE: // special case for interface target if t.Kind() == InterfaceKind { + if tv.IsUndefined() && tv.T == nil { + if _, ok := t.(*NativeType); !ok { // no support for native now + tv.T = t + } + } return } // special case for undefined/nil source @@ -96,7 +101,7 @@ GNO_CASE: validate := func(from Kind, to Kind, cmp func() bool) { if isConst { - msg := fmt.Sprintf("cannot convert constant of type %s to %s\n", from, to) + msg := fmt.Sprintf("cannot convert constant of type %s to %s", from, to) if cmp != nil && cmp() { return } @@ -130,13 +135,13 @@ GNO_CASE: tv.T = t tv.SetInt32(x) case Int64Kind: - x := int64(tv.GetInt()) + x := tv.GetInt() tv.T = t tv.SetInt64(x) case UintKind: validate(IntKind, UintKind, func() bool { return tv.GetInt() >= 0 }) - x := uint(tv.GetInt()) + x := uint64(tv.GetInt()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -164,11 +169,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := softfloat.Fintto32(int64(tv.GetInt())) + x := softfloat.Fintto32(tv.GetInt()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := softfloat.Fintto64(int64(tv.GetInt())) + x := softfloat.Fintto64(tv.GetInt()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -184,7 +189,7 @@ GNO_CASE: case Int8Kind: switch k { case IntKind: - x := int(tv.GetInt8()) + x := int64(tv.GetInt8()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -206,7 +211,7 @@ GNO_CASE: case UintKind: validate(Int8Kind, UintKind, func() bool { return tv.GetInt8() >= 0 }) - x := uint(tv.GetInt8()) + x := uint64(tv.GetInt8()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -253,7 +258,7 @@ GNO_CASE: case Int16Kind: switch k { case IntKind: - x := int(tv.GetInt16()) + x := int64(tv.GetInt16()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -277,7 +282,7 @@ GNO_CASE: case UintKind: validate(Int16Kind, UintKind, func() bool { return tv.GetInt16() >= 0 }) - x := uint(tv.GetInt16()) + x := uint64(tv.GetInt16()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -326,7 +331,7 @@ GNO_CASE: case Int32Kind: switch k { case IntKind: - x := int(tv.GetInt32()) + x := int64(tv.GetInt32()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -352,7 +357,7 @@ GNO_CASE: case UintKind: validate(Int32Kind, UintKind, func() bool { return tv.GetInt32() >= 0 }) - x := uint(tv.GetInt32()) + x := uint64(tv.GetInt32()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -401,7 +406,7 @@ GNO_CASE: case Int64Kind: switch k { case IntKind: - x := int(tv.GetInt64()) + x := tv.GetInt64() tv.T = t tv.SetInt(x) case Int8Kind: @@ -429,7 +434,7 @@ GNO_CASE: case UintKind: validate(Int64Kind, UintKind, func() bool { return tv.GetInt64() >= 0 && uint(tv.GetInt64()) <= math.MaxUint }) - x := uint(tv.GetInt64()) + x := uint64(tv.GetInt64()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -480,7 +485,7 @@ GNO_CASE: case IntKind: validate(UintKind, IntKind, func() bool { return tv.GetUint() <= math.MaxInt }) - x := int(tv.GetUint()) + x := int64(tv.GetUint()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -502,7 +507,7 @@ GNO_CASE: tv.T = t tv.SetInt32(x) case Int64Kind: - validate(UintKind, Int64Kind, func() bool { return uint64(tv.GetUint()) <= math.MaxInt64 }) + validate(UintKind, Int64Kind, func() bool { return tv.GetUint() <= math.MaxInt64 }) x := int64(tv.GetUint()) tv.T = t @@ -530,15 +535,15 @@ GNO_CASE: tv.T = t tv.SetUint32(x) case Uint64Kind: - x := uint64(tv.GetUint()) + x := tv.GetUint() tv.T = t tv.SetUint64(x) case Float32Kind: - x := softfloat.Fuint64to32(uint64(tv.GetUint())) + x := softfloat.Fuint64to32(tv.GetUint()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := softfloat.Fuint64to64(uint64(tv.GetUint())) + x := softfloat.Fuint64to64(tv.GetUint()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -555,7 +560,7 @@ GNO_CASE: case Uint8Kind: switch k { case IntKind: - x := int(tv.GetUint8()) + x := int64(tv.GetUint8()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -565,13 +570,13 @@ GNO_CASE: tv.T = t tv.SetInt8(x) case Int16Kind: - validate(Uint8Kind, Int16Kind, func() bool { return int(tv.GetUint8()) <= math.MaxInt16 }) + validate(Uint8Kind, Int16Kind, func() bool { return int64(tv.GetUint8()) <= math.MaxInt16 }) x := int16(tv.GetUint8()) tv.T = t tv.SetInt16(x) case Int32Kind: - validate(Uint8Kind, Int32Kind, func() bool { return int(tv.GetUint8()) <= math.MaxInt32 }) + validate(Uint8Kind, Int32Kind, func() bool { return int64(tv.GetUint8()) <= math.MaxInt32 }) x := int32(tv.GetUint8()) tv.T = t @@ -583,7 +588,7 @@ GNO_CASE: tv.T = t tv.SetInt64(x) case UintKind: - x := uint(tv.GetUint8()) + x := uint64(tv.GetUint8()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -624,7 +629,7 @@ GNO_CASE: case Uint16Kind: switch k { case IntKind: - x := int(tv.GetUint16()) + x := int64(tv.GetUint16()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -640,7 +645,7 @@ GNO_CASE: tv.T = t tv.SetInt16(x) case Int32Kind: - validate(Uint16Kind, Int32Kind, func() bool { return int(tv.GetUint16()) <= math.MaxInt32 }) + validate(Uint16Kind, Int32Kind, func() bool { return int64(tv.GetUint16()) <= math.MaxInt32 }) x := int32(tv.GetUint16()) tv.T = t @@ -652,11 +657,11 @@ GNO_CASE: tv.T = t tv.SetInt64(x) case UintKind: - x := uint(tv.GetUint16()) + x := uint64(tv.GetUint16()) tv.T = t tv.SetUint(x) case Uint8Kind: - validate(Uint16Kind, Uint8Kind, func() bool { return int(tv.GetUint16()) <= math.MaxUint8 }) + validate(Uint16Kind, Uint8Kind, func() bool { return int64(tv.GetUint16()) <= math.MaxUint8 }) x := uint8(tv.GetUint16()) tv.T = t @@ -695,25 +700,25 @@ GNO_CASE: case Uint32Kind: switch k { case IntKind: - validate(Uint32Kind, IntKind, func() bool { return int(tv.GetUint32()) <= math.MaxInt }) + validate(Uint32Kind, IntKind, func() bool { return int64(tv.GetUint32()) <= math.MaxInt }) - x := int(tv.GetUint32()) + x := int64(tv.GetUint32()) tv.T = t tv.SetInt(x) case Int8Kind: - validate(Uint32Kind, Int8Kind, func() bool { return int(tv.GetUint32()) <= math.MaxInt8 }) + validate(Uint32Kind, Int8Kind, func() bool { return int64(tv.GetUint32()) <= math.MaxInt8 }) x := int8(tv.GetUint32()) tv.T = t tv.SetInt8(x) case Int16Kind: - validate(Uint32Kind, Int16Kind, func() bool { return int(tv.GetUint32()) <= math.MaxInt16 }) + validate(Uint32Kind, Int16Kind, func() bool { return int64(tv.GetUint32()) <= math.MaxInt16 }) x := int16(tv.GetUint32()) tv.T = t tv.SetInt16(x) case Int32Kind: - validate(Uint32Kind, Int32Kind, func() bool { return int(tv.GetUint32()) <= math.MaxInt32 }) + validate(Uint32Kind, Int32Kind, func() bool { return int64(tv.GetUint32()) <= math.MaxInt32 }) x := int32(tv.GetUint32()) tv.T = t @@ -723,9 +728,9 @@ GNO_CASE: tv.T = t tv.SetInt64(x) case UintKind: - x := uint(tv.GetUint32()) + x := uint64(tv.GetUint32()) tv.T = t - tv.SetUint(x) + tv.SetUint64(x) case Uint8Kind: validate(Uint32Kind, Uint8Kind, func() bool { return int(tv.GetUint32()) <= math.MaxUint8 }) @@ -768,25 +773,25 @@ GNO_CASE: case Uint64Kind: switch k { case IntKind: - validate(Uint64Kind, IntKind, func() bool { return int(tv.GetUint64()) <= math.MaxInt }) + validate(Uint64Kind, IntKind, func() bool { return int64(tv.GetUint64()) <= math.MaxInt }) - x := int(tv.GetUint64()) + x := int64(tv.GetUint64()) tv.T = t tv.SetInt(x) case Int8Kind: - validate(Uint64Kind, Int8Kind, func() bool { return int(tv.GetUint64()) <= math.MaxInt8 }) + validate(Uint64Kind, Int8Kind, func() bool { return int64(tv.GetUint64()) <= math.MaxInt8 }) x := int8(tv.GetUint64()) tv.T = t tv.SetInt8(x) case Int16Kind: - validate(Uint64Kind, Int16Kind, func() bool { return int(tv.GetUint64()) <= math.MaxInt16 }) + validate(Uint64Kind, Int16Kind, func() bool { return int64(tv.GetUint64()) <= math.MaxInt16 }) x := int16(tv.GetUint64()) tv.T = t tv.SetInt16(x) case Int32Kind: - validate(Uint64Kind, Int32Kind, func() bool { return int(tv.GetUint64()) <= math.MaxInt32 }) + validate(Uint64Kind, Int32Kind, func() bool { return int64(tv.GetUint64()) <= math.MaxInt32 }) x := int32(tv.GetUint64()) tv.T = t @@ -800,17 +805,17 @@ GNO_CASE: case UintKind: validate(Uint64Kind, UintKind, func() bool { return tv.GetUint64() <= math.MaxUint }) - x := uint(tv.GetUint64()) + x := tv.GetUint64() tv.T = t - tv.SetUint(x) + tv.SetUint64(x) case Uint8Kind: - validate(Uint64Kind, Uint8Kind, func() bool { return int(tv.GetUint64()) <= math.MaxUint8 }) + validate(Uint64Kind, Uint8Kind, func() bool { return int64(tv.GetUint64()) <= math.MaxUint8 }) x := uint8(tv.GetUint64()) tv.T = t tv.SetUint8(x) case Uint16Kind: - validate(Uint64Kind, Uint16Kind, func() bool { return int(tv.GetUint64()) <= math.MaxUint16 }) + validate(Uint64Kind, Uint16Kind, func() bool { return int64(tv.GetUint64()) <= math.MaxUint16 }) x := uint16(tv.GetUint64()) tv.T = t @@ -858,7 +863,7 @@ GNO_CASE: return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt }) - x := int(softfloat.F32toint64(tv.GetFloat32())) + x := softfloat.F32toint64(tv.GetFloat32()) tv.T = t tv.SetInt(x) case Int8Kind: @@ -928,7 +933,7 @@ GNO_CASE: return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint(softfloat.F32touint64(tv.GetFloat32())) + x := softfloat.F32touint64(tv.GetFloat32()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -1019,9 +1024,8 @@ GNO_CASE: }) xp, _ := softfloat.F64toint(tv.GetFloat64()) - x := int(xp) tv.T = t - tv.SetInt(x) + tv.SetInt(xp) case Int8Kind: validate(Float64Kind, Int8Kind, func() bool { trunc := softfloat.Ftrunc64(tv.GetFloat64()) @@ -1090,7 +1094,7 @@ GNO_CASE: return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint(softfloat.F64touint64(tv.GetFloat64())) + x := softfloat.F64touint64(tv.GetFloat64()) tv.T = t tv.SetUint(x) case Uint8Kind: @@ -1312,28 +1316,14 @@ func ConvertUntypedTo(tv *TypedValue, t Type) { } switch tv.T { case UntypedBoolType: - if debug { - if t.Kind() != BoolKind { - panic("untyped bool can only be converted to bool kind") - } - } tv.T = t case UntypedRuneType: ConvertUntypedRuneTo(tv, t) case UntypedBigintType: - if preprocessing.Load() == 0 { - panic("untyped Bigint conversion should not happen during interpretation") - } ConvertUntypedBigintTo(tv, tv.V.(BigintValue), t) case UntypedBigdecType: - if preprocessing.Load() == 0 { - panic("untyped Bigdec conversion should not happen during interpretation") - } ConvertUntypedBigdecTo(tv, tv.V.(BigdecValue), t) case UntypedStringType: - if preprocessing.Load() == 0 { - panic("untyped String conversion should not happen during interpretation") - } if t.Kind() == StringKind { tv.T = t return @@ -1372,7 +1362,7 @@ func ConvertUntypedRuneTo(dst *TypedValue, t Type) { // the value is within int64 or uint64. switch k { case IntKind: - dst.SetInt(int(sv)) + dst.SetInt(int64(sv)) case Int8Kind: if math.MaxInt8 < sv { panic("rune overflows target kind") @@ -1397,7 +1387,7 @@ func ConvertUntypedRuneTo(dst *TypedValue, t Type) { if sv < 0 { panic("rune underflows target kind") } - dst.SetUint(uint(sv)) + dst.SetUint(uint64(sv)) case Uint8Kind: if sv < 0 { panic("rune underflows target kind") @@ -1523,9 +1513,9 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { if sv < math.MinInt32 { panic("bigint underflows target kind") } - dst.SetInt(int(sv)) + dst.SetInt(sv) } else if strconv.IntSize == 64 { - dst.SetInt(int(sv)) + dst.SetInt(sv) } else { panic("unexpected IntSize") } @@ -1560,9 +1550,9 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { if math.MaxUint32 < uv { panic("bigint overflows target kind") } - dst.SetUint(uint(uv)) + dst.SetUint(uv) } else if strconv.IntSize == 64 { - dst.SetUint(uint(uv)) + dst.SetUint(uv) } else { panic("unexpected IntSize") } diff --git a/gnovm/pkg/gnolang/values_fill.go b/gnovm/pkg/gnolang/values_fill.go new file mode 100644 index 00000000000..53a91c106b3 --- /dev/null +++ b/gnovm/pkg/gnolang/values_fill.go @@ -0,0 +1,86 @@ +package gnolang + +func (sv StringValue) DeepFill(store Store) Value { + return sv +} + +func (biv BigintValue) DeepFill(store Store) Value { + return biv +} + +func (bdv BigdecValue) DeepFill(store Store) Value { + return bdv +} + +func (dbv DataByteValue) DeepFill(store Store) Value { + dbv.Base.DeepFill(store) + return dbv +} + +func (pv PointerValue) DeepFill(store Store) Value { + if pv.Key != nil { + // only used transiently for assignment! + panic("should not happen") + } + // No need to fill pv.TV.V because + // either it will be filled with .Base, + // or, it was never persisted anyways. + if pv.Base != nil { + return PointerValue{ + TV: pv.TV, + Base: pv.Base.DeepFill(store), + Index: pv.Index, + Key: nil, + } + } + return pv +} + +func (av *ArrayValue) DeepFill(store Store) Value { + if av.List != nil { + for i := range len(av.List) { + tv := &av.List[i] + if tv.V != nil { + tv.V = tv.V.DeepFill(store) + } + } + } + return av +} + +func (sv *SliceValue) DeepFill(store Store) Value { + if sv.Base != nil { + sv.Base = sv.Base.DeepFill(store) + } + return sv +} + +func (sv *StructValue) DeepFill(store Store) Value { + for i := range len(sv.Fields) { + tv := &sv.Fields[i] + if tv.V != nil { + tv.V = tv.V.DeepFill(store) + } + } + return sv +} + +// XXX implement these too +func (fv *FuncValue) DeepFill(store Store) Value { panic("not yet implemented") } +func (mv *MapValue) DeepFill(store Store) Value { panic("not yet implemented") } +func (bmv *BoundMethodValue) DeepFill(store Store) Value { panic("not yet implemented") } +func (tv TypeValue) DeepFill(store Store) Value { panic("not yet implemented") } +func (pv *PackageValue) DeepFill(store Store) Value { panic("not yet implemented") } +func (nv *NativeValue) DeepFill(store Store) Value { panic("not yet implemented") } +func (b *Block) DeepFill(store Store) Value { panic("not yet implemented") } + +func (rv RefValue) DeepFill(store Store) Value { + return store.GetObject(rv.ObjectID) +} + +func (hiv *HeapItemValue) DeepFill(store Store) Value { + if hiv.Value.V != nil { + hiv.Value.V = hiv.Value.V.DeepFill(store) + } + return hiv +} diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index fdf0c8f55de..f0421914f1b 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -12,11 +12,17 @@ type protectedStringer interface { ProtectedString(*seenValues) string } -// This indicates the maximum anticipated depth of the stack when printing a Value type. -const defaultSeenValuesSize = 32 +const ( + // defaultSeenValuesSize indicates the maximum anticipated depth of the stack when printing a Value type. + defaultSeenValuesSize = 32 + + // nestedLimit indicates the maximum nested level when printing a deeply recursive value. + nestedLimit = 10 +) type seenValues struct { values []Value + nc int // nested counter, to limit recursivity } func (sv *seenValues) Put(v Value) { @@ -46,19 +52,19 @@ func (sv *seenValues) Pop() { } func newSeenValues() *seenValues { - return &seenValues{values: make([]Value, 0, defaultSeenValuesSize)} + return &seenValues{values: make([]Value, 0, defaultSeenValuesSize), nc: nestedLimit} } -func (v StringValue) String() string { - return strconv.Quote(string(v)) +func (sv StringValue) String() string { + return strconv.Quote(string(sv)) } -func (bv BigintValue) String() string { - return bv.V.String() +func (biv BigintValue) String() string { + return biv.V.String() } -func (bv BigdecValue) String() string { - return bv.V.String() +func (bdv BigdecValue) String() string { + return bdv.V.String() } func (dbv DataByteValue) String() string { @@ -74,6 +80,10 @@ func (av *ArrayValue) ProtectedString(seen *seenValues) string { return fmt.Sprintf("%p", av) } + seen.nc-- + if seen.nc < 0 { + return "..." + } seen.Put(av) defer seen.Pop() @@ -177,18 +187,18 @@ func (fv *FuncValue) String() string { return name } -func (v *BoundMethodValue) String() string { - name := v.Func.Name +func (bmv *BoundMethodValue) String() string { + name := bmv.Func.Name var ( recvT string = "?" params string = "?" results string = "(?)" ) - if ft, ok := v.Func.Type.(*FuncType); ok { + if ft, ok := bmv.Func.Type.(*FuncType); ok { recvT = ft.Params[0].Type.String() - params = FieldTypeList(ft.Params).StringWithCommas() + params = FieldTypeList(ft.Params).StringForFunc() if len(results) > 0 { - results = FieldTypeList(ft.Results).StringWithCommas() + results = FieldTypeList(ft.Results).StringForFunc() results = "(" + results + ")" } } @@ -223,19 +233,19 @@ func (mv *MapValue) ProtectedString(seen *seenValues) string { return "map{" + strings.Join(ss, ",") + "}" } -func (v TypeValue) String() string { +func (tv TypeValue) String() string { ptr := "" - if reflect.TypeOf(v.Type).Kind() == reflect.Ptr { - ptr = fmt.Sprintf(" (%p)", v.Type) + if reflect.TypeOf(tv.Type).Kind() == reflect.Ptr { + ptr = fmt.Sprintf(" (%p)", tv.Type) } /* mthds := "" - if d, ok := v.Type.(*DeclaredType); ok { + if d, ok := tv.Type.(*DeclaredType); ok { mthds = fmt.Sprintf(" %v", d.Methods) } */ return fmt.Sprintf("typeval{%s%s}", - v.Type.String(), ptr) + tv.Type.String(), ptr) } func (pv *PackageValue) String() string { @@ -253,18 +263,18 @@ func (nv *NativeValue) String() string { */ } -func (v RefValue) String() string { - if v.PkgPath == "" { +func (rv RefValue) String() string { + if rv.PkgPath == "" { return fmt.Sprintf("ref(%v)", - v.ObjectID) + rv.ObjectID) } return fmt.Sprintf("ref(%s)", - v.PkgPath) + rv.PkgPath) } -func (v *HeapItemValue) String() string { +func (hiv *HeapItemValue) String() string { return fmt.Sprintf("heapitem(%v)", - v.Value) + hiv.Value) } // ---------------------------------------- @@ -278,7 +288,7 @@ func (tv *TypedValue) Sprint(m *Machine) string { } // if implements .String(), return it. - if IsImplementedBy(gStringerType, tv.T) { + if IsImplementedBy(gStringerType, tv.T) && !tv.IsNilInterface() { res := m.Eval(Call(Sel(&ConstExpr{TypedValue: *tv}, "String"))) return res[0].GetString() } @@ -343,9 +353,9 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo return fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32())) case Float64Type: return fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64())) - case UntypedBigintType, BigintType: + case UntypedBigintType: return tv.V.(BigintValue).V.String() - case UntypedBigdecType, BigdecType: + case UntypedBigdecType: return tv.V.(BigdecValue).V.String() default: panic("should not happen") diff --git a/gnovm/pkg/gnolang/values_test.go b/gnovm/pkg/gnolang/values_test.go index ce6edd0a2f9..ab1829abe7e 100644 --- a/gnovm/pkg/gnolang/values_test.go +++ b/gnovm/pkg/gnolang/values_test.go @@ -15,6 +15,10 @@ func (m *mockTypedValueStruct) String() string { return fmt.Sprintf("MockTypedValueStruct(%d)", m.field) } +func (m *mockTypedValueStruct) DeepFill(store Store) Value { + return m +} + func TestGetLengthPanic(t *testing.T) { tests := []struct { name string diff --git a/gnovm/pkg/gnolang/vptype_string.go b/gnovm/pkg/gnolang/vptype_string.go deleted file mode 100644 index 62a51c3b256..00000000000 --- a/gnovm/pkg/gnolang/vptype_string.go +++ /dev/null @@ -1,48 +0,0 @@ -// Code generated by "stringer -type=VPType ./pkg/gnolang"; DO NOT EDIT. - -package gnolang - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[VPUverse-0] - _ = x[VPBlock-1] - _ = x[VPField-2] - _ = x[VPValMethod-3] - _ = x[VPPtrMethod-4] - _ = x[VPInterface-5] - _ = x[VPSubrefField-6] - _ = x[VPDerefField-18] - _ = x[VPDerefValMethod-19] - _ = x[VPDerefPtrMethod-20] - _ = x[VPDerefInterface-21] - _ = x[VPNative-32] -} - -const ( - _VPType_name_0 = "VPUverseVPBlockVPFieldVPValMethodVPPtrMethodVPInterfaceVPSubrefField" - _VPType_name_1 = "VPDerefFieldVPDerefValMethodVPDerefPtrMethodVPDerefInterface" - _VPType_name_2 = "VPNative" -) - -var ( - _VPType_index_0 = [...]uint8{0, 8, 15, 22, 33, 44, 55, 68} - _VPType_index_1 = [...]uint8{0, 12, 28, 44, 60} -) - -func (i VPType) String() string { - switch { - case i <= 6: - return _VPType_name_0[_VPType_index_0[i]:_VPType_index_0[i+1]] - case 18 <= i && i <= 21: - i -= 18 - return _VPType_name_1[_VPType_index_1[i]:_VPType_index_1[i+1]] - case i == 32: - return _VPType_name_2 - default: - return "VPType(" + strconv.FormatInt(int64(i), 10) + ")" - } -} diff --git a/gnovm/pkg/gnolang/word_string.go b/gnovm/pkg/gnolang/word_string.go deleted file mode 100644 index da5fc3d7412..00000000000 --- a/gnovm/pkg/gnolang/word_string.go +++ /dev/null @@ -1,90 +0,0 @@ -// Code generated by "stringer -type=Word ./pkg/gnolang"; DO NOT EDIT. - -package gnolang - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[ILLEGAL-0] - _ = x[NAME-1] - _ = x[INT-2] - _ = x[FLOAT-3] - _ = x[IMAG-4] - _ = x[CHAR-5] - _ = x[STRING-6] - _ = x[ADD-7] - _ = x[SUB-8] - _ = x[MUL-9] - _ = x[QUO-10] - _ = x[REM-11] - _ = x[BAND-12] - _ = x[BOR-13] - _ = x[XOR-14] - _ = x[SHL-15] - _ = x[SHR-16] - _ = x[BAND_NOT-17] - _ = x[ADD_ASSIGN-18] - _ = x[SUB_ASSIGN-19] - _ = x[MUL_ASSIGN-20] - _ = x[QUO_ASSIGN-21] - _ = x[REM_ASSIGN-22] - _ = x[BAND_ASSIGN-23] - _ = x[BOR_ASSIGN-24] - _ = x[XOR_ASSIGN-25] - _ = x[SHL_ASSIGN-26] - _ = x[SHR_ASSIGN-27] - _ = x[BAND_NOT_ASSIGN-28] - _ = x[LAND-29] - _ = x[LOR-30] - _ = x[ARROW-31] - _ = x[INC-32] - _ = x[DEC-33] - _ = x[EQL-34] - _ = x[LSS-35] - _ = x[GTR-36] - _ = x[ASSIGN-37] - _ = x[NOT-38] - _ = x[NEQ-39] - _ = x[LEQ-40] - _ = x[GEQ-41] - _ = x[DEFINE-42] - _ = x[BREAK-43] - _ = x[CASE-44] - _ = x[CHAN-45] - _ = x[CONST-46] - _ = x[CONTINUE-47] - _ = x[DEFAULT-48] - _ = x[DEFER-49] - _ = x[ELSE-50] - _ = x[FALLTHROUGH-51] - _ = x[FOR-52] - _ = x[FUNC-53] - _ = x[GO-54] - _ = x[GOTO-55] - _ = x[IF-56] - _ = x[IMPORT-57] - _ = x[INTERFACE-58] - _ = x[MAP-59] - _ = x[PACKAGE-60] - _ = x[RANGE-61] - _ = x[RETURN-62] - _ = x[SELECT-63] - _ = x[STRUCT-64] - _ = x[SWITCH-65] - _ = x[TYPE-66] - _ = x[VAR-67] -} - -const _Word_name = "ILLEGALNAMEINTFLOATIMAGCHARSTRINGADDSUBMULQUOREMBANDBORXORSHLSHRBAND_NOTADD_ASSIGNSUB_ASSIGNMUL_ASSIGNQUO_ASSIGNREM_ASSIGNBAND_ASSIGNBOR_ASSIGNXOR_ASSIGNSHL_ASSIGNSHR_ASSIGNBAND_NOT_ASSIGNLANDLORARROWINCDECEQLLSSGTRASSIGNNOTNEQLEQGEQDEFINEBREAKCASECHANCONSTCONTINUEDEFAULTDEFERELSEFALLTHROUGHFORFUNCGOGOTOIFIMPORTINTERFACEMAPPACKAGERANGERETURNSELECTSTRUCTSWITCHTYPEVAR" - -var _Word_index = [...]uint16{0, 7, 11, 14, 19, 23, 27, 33, 36, 39, 42, 45, 48, 52, 55, 58, 61, 64, 72, 82, 92, 102, 112, 122, 133, 143, 153, 163, 173, 188, 192, 195, 200, 203, 206, 209, 212, 215, 221, 224, 227, 230, 233, 239, 244, 248, 252, 257, 265, 272, 277, 281, 292, 295, 299, 301, 305, 307, 313, 322, 325, 332, 337, 343, 349, 355, 361, 365, 368} - -func (i Word) String() string { - if i < 0 || i >= Word(len(_Word_index)-1) { - return "Word(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _Word_name[_Word_index[i]:_Word_index[i+1]] -} diff --git a/gnovm/pkg/packages/imports_test.go b/gnovm/pkg/packages/imports_test.go index f9f58b967c8..444964fbbf3 100644 --- a/gnovm/pkg/packages/imports_test.go +++ b/gnovm/pkg/packages/imports_test.go @@ -1,4 +1,4 @@ -package packages +package packages_test import ( "os" @@ -6,6 +6,7 @@ import ( "testing" "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/packages" "github.com/stretchr/testify/require" ) @@ -112,22 +113,22 @@ func TestImports(t *testing.T) { // - ignore subdirs // - ignore duplicate // - should be sorted - expected := map[FileKind][]string{ - FileKindPackageSource: { + expected := map[packages.FileKind][]string{ + packages.FileKindPackageSource: { "gno.land/p/demo/pkg1", "gno.land/p/demo/pkg2", "std", }, - FileKindTest: { + packages.FileKindTest: { "gno.land/p/demo/testpkg", "testing", }, - FileKindXTest: { + packages.FileKindXTest: { "gno.land/p/demo/testpkg", "gno.land/p/demo/xtestdep", "testing", }, - FileKindFiletest: { + packages.FileKindFiletest: { "gno.land/p/demo/filetestdep", }, } @@ -145,11 +146,11 @@ func TestImports(t *testing.T) { pkg, err := gnolang.ReadMemPackage(tmpDir, "test") require.NoError(t, err) - importsMap, err := Imports(pkg, nil) + importsMap, err := packages.Imports(pkg, nil) require.NoError(t, err) // ignore specs - got := map[FileKind][]string{} + got := map[packages.FileKind][]string{} for key, vals := range importsMap { stringVals := make([]string, len(vals)) for i, val := range vals { diff --git a/gnovm/pkg/repl/repl.go b/gnovm/pkg/repl/repl.go index b0944d21646..40f14628678 100644 --- a/gnovm/pkg/repl/repl.go +++ b/gnovm/pkg/repl/repl.go @@ -125,7 +125,7 @@ func NewRepl(opts ...ReplOption) *Repl { r.stderr = &b r.storeFunc = func() gno.Store { - _, st := test.Store(gnoenv.RootDir(), false, r.stdin, r.stdout, r.stderr) + _, st := test.Store(gnoenv.RootDir(), test.OutputWithError(r.stdout, r.stderr)) return st } @@ -133,7 +133,7 @@ func NewRepl(opts ...ReplOption) *Repl { o(r) } - r.state = newState(r.stdout, r.storeFunc) + r.state = newState(test.OutputWithError(r.stdout, r.stderr), r.storeFunc) br := bufio.NewReader(r.stdin) bw := bufio.NewWriter(io.MultiWriter(r.stderr, r.stdout)) @@ -156,7 +156,7 @@ func (r *Repl) Process(input string) (out string, err error) { r.state.id++ if r.debug { - r.state.machine.Debugger.Enable(os.Stdin, os.Stdout, func(file string) string { + r.state.machine.Debugger.Enable(os.Stdin, os.Stdout, func(ppath, file string) string { return r.state.files[file] }) r.debug = false diff --git a/gnovm/pkg/test/filetest.go b/gnovm/pkg/test/filetest.go index 1934f429568..18b3fc2cab6 100644 --- a/gnovm/pkg/test/filetest.go +++ b/gnovm/pkg/test/filetest.go @@ -24,6 +24,7 @@ import ( // the first string is set to the new generated content of the file. func (opts *TestOptions) RunFiletest(filename string, source []byte) (string, error) { opts.outWriter.w = opts.Output + opts.outWriter.errW = opts.Error return opts.runFiletest(filename, source) } @@ -63,6 +64,7 @@ func (opts *TestOptions) runFiletest(filename string, source []byte) (string, er Store: opts.TestStore.BeginTransaction(cw, cw, nil), Context: ctx, MaxAllocBytes: maxAlloc, + Debug: opts.Debug, }) defer m.Release() result := opts.runTest(m, pkgPath, filename, source) @@ -103,6 +105,11 @@ func (opts *TestOptions) runFiletest(filename string, source []byte) (string, er // The Error directive (and many others) will have one trailing newline, // which is not in the output - so add it there. match(errDirective, result.Error+"\n") + } else if result.Output != "" { + outputDirective := dirs.First(DirectiveOutput) + if outputDirective == nil { + return "", fmt.Errorf("unexpected output:\n%s", result.Output) + } } else { err = m.CheckEmpty() if err != nil { diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index a8dd709e501..de572ab0af0 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -25,22 +25,54 @@ import ( storetypes "github.com/gnolang/gno/tm2/pkg/store/types" ) +type StoreOptions struct { + // WithExtern interprets imports of packages under "github.com/gnolang/gno/_test/" + // as imports under the directory in gnovm/tests/files/extern. + // This should only be used for GnoVM internal filetests (gnovm/tests/files). + WithExtern bool + + // PreprocessOnly instructs the PackageGetter to run the imported files using + // [gno.Machine.PreprocessFiles]. It avoids executing code for contexts + // which only intend to perform a type check, ie. `gno lint`. + PreprocessOnly bool +} + // NOTE: this isn't safe, should only be used for testing. func Store( rootDir string, - withExtern bool, - stdin io.Reader, - stdout, stderr io.Writer, + output io.Writer, +) ( + baseStore storetypes.CommitStore, + resStore gno.Store, +) { + return StoreWithOptions(rootDir, output, StoreOptions{}) +} + +// StoreWithOptions is a variant of [Store] which additionally accepts a +// [StoreOptions] argument. +func StoreWithOptions( + rootDir string, + output io.Writer, + opts StoreOptions, ) ( baseStore storetypes.CommitStore, resStore gno.Store, ) { + processMemPackage := func(m *gno.Machine, memPkg *gnovm.MemPackage, save bool) (*gno.PackageNode, *gno.PackageValue) { + return m.RunMemPackage(memPkg, save) + } + if opts.PreprocessOnly { + processMemPackage = func(m *gno.Machine, memPkg *gnovm.MemPackage, save bool) (*gno.PackageNode, *gno.PackageValue) { + m.Store.AddMemPackage(memPkg) + return m.PreprocessFiles(memPkg.Name, memPkg.Path, gno.ParseMemPackage(memPkg), save, false) + } + } getPackage := func(pkgPath string, store gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } - if withExtern { + if opts.WithExtern { // if _test package... const testPath = "github.com/gnolang/gno/_test/" if strings.HasPrefix(pkgPath, testPath) { @@ -50,54 +82,23 @@ func Store( ctx := Context(pkgPath, send) m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", - Output: stdout, + Output: output, Store: store, Context: ctx, }) - return m2.RunMemPackage(memPkg, true) + return processMemPackage(m2, memPkg, true) } } // gonative exceptions. // These are values available using gonative; eventually they should all be removed. switch pkgPath { - case "os": - pkg := gno.NewPackageNode("os", pkgPath, nil) - pkg.DefineGoNativeValue("Stdin", stdin) - pkg.DefineGoNativeValue("Stdout", stdout) - pkg.DefineGoNativeValue("Stderr", stderr) - return pkg, pkg.NewPackage() - case "fmt": - pkg := gno.NewPackageNode("fmt", pkgPath, nil) - pkg.DefineGoNativeValue("Println", func(a ...interface{}) (n int, err error) { - // NOTE: uncomment to debug long running tests - // fmt.Println(a...) - res := fmt.Sprintln(a...) - return stdout.Write([]byte(res)) - }) - pkg.DefineGoNativeValue("Printf", func(format string, a ...interface{}) (n int, err error) { - res := fmt.Sprintf(format, a...) - return stdout.Write([]byte(res)) - }) - pkg.DefineGoNativeValue("Print", func(a ...interface{}) (n int, err error) { - res := fmt.Sprint(a...) - return stdout.Write([]byte(res)) - }) - pkg.DefineGoNativeValue("Sprint", fmt.Sprint) - pkg.DefineGoNativeValue("Sprintf", fmt.Sprintf) - pkg.DefineGoNativeValue("Sprintln", fmt.Sprintln) - pkg.DefineGoNativeValue("Sscanf", fmt.Sscanf) - pkg.DefineGoNativeValue("Errorf", fmt.Errorf) - pkg.DefineGoNativeValue("Fprintln", fmt.Fprintln) - pkg.DefineGoNativeValue("Fprintf", fmt.Fprintf) - pkg.DefineGoNativeValue("Fprint", fmt.Fprint) - return pkg, pkg.NewPackage() case "encoding/json": pkg := gno.NewPackageNode("json", pkgPath, nil) pkg.DefineGoNativeValue("Unmarshal", json.Unmarshal) pkg.DefineGoNativeValue("Marshal", json.Marshal) return pkg, pkg.NewPackage() - case "internal/os_test": + case "os_test": pkg := gno.NewPackageNode("os_test", pkgPath, nil) pkg.DefineNative("Sleep", gno.Flds( // params @@ -129,7 +130,7 @@ func Store( } // Load normal stdlib. - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, store, output, opts.PreprocessOnly) if pn != nil { return } @@ -146,12 +147,11 @@ func Store( ctx := Context(pkgPath, send) m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", - Output: stdout, + Output: output, Store: store, Context: ctx, }) - pn, pv = m2.RunMemPackage(memPkg, true) - return + return processMemPackage(m2, memPkg, true) } return nil, nil } @@ -164,7 +164,7 @@ func Store( return } -func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gno.PackageNode, *gno.PackageValue) { +func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer, preprocessOnly bool) (*gno.PackageNode, *gno.PackageValue) { dirs := [...]string{ // Normal stdlib path. filepath.Join(rootDir, "gnovm", "stdlibs", pkgPath), @@ -202,6 +202,11 @@ func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gn Output: stdout, Store: store, }) + if preprocessOnly { + m2.Store.AddMemPackage(memPkg) + return m2.PreprocessFiles(memPkg.Name, memPkg.Path, gno.ParseMemPackage(memPkg), true, true) + } + // TODO: make this work when using gno lint. return m2.RunMemPackageWithOverrides(memPkg, true) } diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index d06540761d7..67d3a5fd33c 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -51,17 +51,17 @@ func Context(pkgPath string, send std.Coins) *teststd.TestExecContext { }, } ctx := stdlibs.ExecContext{ - ChainID: "dev", - ChainDomain: "tests.gno.land", - Height: DefaultHeight, - Timestamp: DefaultTimestamp, - OrigCaller: DefaultCaller, - OrigPkgAddr: pkgAddr.Bech32(), - OrigSend: send, - OrigSendSpent: new(std.Coins), - Banker: banker, - Params: newTestParams(), - EventLogger: sdk.NewEventLogger(), + ChainID: "dev", + ChainDomain: "tests.gno.land", + Height: DefaultHeight, + Timestamp: DefaultTimestamp, + OriginCaller: DefaultCaller, + OriginPkgAddr: pkgAddr.Bech32(), + OriginSend: send, + OriginSendSpent: new(std.Coins), + Banker: banker, + Params: newTestParams(), + EventLogger: sdk.NewEventLogger(), } return &teststd.TestExecContext{ ExecContext: ctx, @@ -70,14 +70,30 @@ func Context(pkgPath string, send std.Coins) *teststd.TestExecContext { } // Machine is a minimal machine, set up with just the Store, Output and Context. -func Machine(testStore gno.Store, output io.Writer, pkgPath string) *gno.Machine { +func Machine(testStore gno.Store, output io.Writer, pkgPath string, debug bool) *gno.Machine { return gno.NewMachineWithOptions(gno.MachineOptions{ Store: testStore, Output: output, Context: Context(pkgPath, nil), + Debug: debug, }) } +// OutputWithError returns an io.Writer that can be used as a [gno.Machine.Output], +// where the test standard libraries will write to errWriter when using +// os.Stderr. +func OutputWithError(output, errWriter io.Writer) io.Writer { + return &outputWithError{output, errWriter} +} + +type outputWithError struct { + w io.Writer + errW io.Writer +} + +func (o outputWithError) Write(p []byte) (int, error) { return o.w.Write(p) } +func (o outputWithError) StderrWrite(p []byte) (int, error) { return o.errW.Write(p) } + // ---------------------------------------- // testParams @@ -87,11 +103,12 @@ func newTestParams() *testParams { return &testParams{} } -func (tp *testParams) SetBool(key string, val bool) { /* noop */ } -func (tp *testParams) SetBytes(key string, val []byte) { /* noop */ } -func (tp *testParams) SetInt64(key string, val int64) { /* noop */ } -func (tp *testParams) SetUint64(key string, val uint64) { /* noop */ } -func (tp *testParams) SetString(key string, val string) { /* noop */ } +func (tp *testParams) SetBool(key string, val bool) { /* noop */ } +func (tp *testParams) SetBytes(key string, val []byte) { /* noop */ } +func (tp *testParams) SetInt64(key string, val int64) { /* noop */ } +func (tp *testParams) SetUint64(key string, val uint64) { /* noop */ } +func (tp *testParams) SetString(key string, val string) { /* noop */ } +func (tp *testParams) SetStrings(key string, val []string) { /* noop */ } // ---------------------------------------- // main test function @@ -107,6 +124,8 @@ type TestOptions struct { Output io.Writer // Used for os.Stderr, and for printing errors. Error io.Writer + // Debug enables the interactive debugger on gno tests. + Debug bool // Not set by NewTestOptions: @@ -133,40 +152,53 @@ func (opts *TestOptions) WriterForStore() io.Writer { } // NewTestOptions sets up TestOptions, filling out all "required" parameters. -func NewTestOptions(rootDir string, stdin io.Reader, stdout, stderr io.Writer) *TestOptions { +func NewTestOptions(rootDir string, stdout, stderr io.Writer) *TestOptions { opts := &TestOptions{ RootDir: rootDir, Output: stdout, Error: stderr, } - opts.BaseStore, opts.TestStore = Store( - rootDir, false, - stdin, opts.WriterForStore(), stderr, - ) + opts.BaseStore, opts.TestStore = Store(rootDir, opts.WriterForStore()) return opts } // proxyWriter is a simple wrapper around a io.Writer, it exists so that the // underlying writer can be swapped with another when necessary. type proxyWriter struct { - w io.Writer + w io.Writer + errW io.Writer } func (p *proxyWriter) Write(b []byte) (int, error) { return p.w.Write(b) } +// StderrWrite implements the interface specified in tests/stdlibs/os/os.go, +// which if found in Machine.Output allows to write to stderr from Gno. +func (p *proxyWriter) StderrWrite(b []byte) (int, error) { + return p.errW.Write(b) +} + // tee temporarily appends the writer w to an underlying MultiWriter, which // should then be reverted using revert(). func (p *proxyWriter) tee(w io.Writer) (revert func()) { - save := p.w + rev := tee(&p.w, w) + revErr := tee(&p.errW, w) + return func() { + rev() + revErr() + } +} + +func tee(ptr *io.Writer, dst io.Writer) (revert func()) { + save := *ptr if save == io.Discard { - p.w = w + *ptr = dst } else { - p.w = io.MultiWriter(save, w) + *ptr = io.MultiWriter(save, dst) } return func() { - p.w = save + *ptr = save } } @@ -178,6 +210,7 @@ func (p *proxyWriter) tee(w io.Writer) (revert func()) { // tests; you can use [NewTestOptions] for a common base configuration. func Test(memPkg *gnovm.MemPackage, fsDir string, opts *TestOptions) error { opts.outWriter.w = opts.Output + opts.outWriter.errW = opts.Error var errs error @@ -189,7 +222,7 @@ func Test(memPkg *gnovm.MemPackage, fsDir string, opts *TestOptions) error { // Stands for "test", "integration test", and "filetest". // "integration test" are the test files with `package xxx_test` (they are // not necessarily integration tests, it's just for our internal reference.) - tset, itset, itfiles, ftfiles := parseMemPackageTests(opts.TestStore, memPkg) + tset, itset, itfiles, ftfiles := parseMemPackageTests(memPkg) // Testing with *_test.gno if len(tset.Files)+len(itset.Files) > 0 { @@ -201,7 +234,7 @@ func Test(memPkg *gnovm.MemPackage, fsDir string, opts *TestOptions) error { // Run test files in pkg. if len(tset.Files) > 0 { - err := opts.runTestFiles(memPkg, tset, cw, gs) + err := opts.runTestFiles(memPkg, tset, gs) if err != nil { errs = multierr.Append(errs, err) } @@ -215,7 +248,7 @@ func Test(memPkg *gnovm.MemPackage, fsDir string, opts *TestOptions) error { Files: itfiles, } - err := opts.runTestFiles(itPkg, itset, cw, gs) + err := opts.runTestFiles(itPkg, itset, gs) if err != nil { errs = multierr.Append(errs, err) } @@ -267,7 +300,7 @@ func Test(memPkg *gnovm.MemPackage, fsDir string, opts *TestOptions) error { func (opts *TestOptions) runTestFiles( memPkg *gnovm.MemPackage, files *gno.FileSet, - cw storetypes.Store, gs gno.TransactionStore, + gs gno.TransactionStore, ) (errs error) { var m *gno.Machine defer func() { @@ -292,11 +325,10 @@ func (opts *TestOptions) runTestFiles( // reset store ops, if any - we only need them for some filetests. opts.TestStore.SetLogStoreOps(false) - // Check if we already have the package - it may have been eagerly - // loaded. - m = Machine(gs, opts.WriterForStore(), memPkg.Path) + // Check if we already have the package - it may have been eagerly loaded. + m = Machine(gs, opts.WriterForStore(), memPkg.Path, opts.Debug) m.Alloc = alloc - if opts.TestStore.GetMemPackage(memPkg.Path) == nil { + if gs.GetMemPackage(memPkg.Path) == nil { m.RunMemPackage(memPkg, true) } else { m.SetActivePackage(gs.GetPackage(memPkg.Path, false)) @@ -314,7 +346,7 @@ func (opts *TestOptions) runTestFiles( // - Run the test files before this for loop (but persist it to store; // RunFiles doesn't do that currently) // - Wrap here. - m = Machine(gs, opts.Output, memPkg.Path) + m = Machine(gs, opts.WriterForStore(), memPkg.Path, opts.Debug) m.Alloc = alloc.Reset() m.SetActivePackage(pv) @@ -322,6 +354,23 @@ func (opts *TestOptions) runTestFiles( testingtv := gno.TypedValue{T: &gno.PackageType{}, V: testingpv} testingcx := &gno.ConstExpr{TypedValue: testingtv} + if opts.Debug { + fileContent := func(ppath, name string) string { + p := filepath.Join(opts.RootDir, ppath, name) + b, err := os.ReadFile(p) + if err != nil { + p = filepath.Join(opts.RootDir, "gnovm", "stdlibs", ppath, name) + b, err = os.ReadFile(p) + } + if err != nil { + p = filepath.Join(opts.RootDir, "examples", ppath, name) + b, err = os.ReadFile(p) + } + return string(b) + } + m.Debugger.Enable(os.Stdin, os.Stdout, fileContent) + } + eval := m.Eval(gno.Call( gno.Sel(testingcx, "RunTest"), // Call testing.RunTest gno.Str(opts.RunFlag), // run flag @@ -419,7 +468,7 @@ func loadTestFuncs(pkgName string, tfiles *gno.FileSet) (rt []testFunc) { } // parseMemPackageTests parses test files (skipping filetests) in the memPkg. -func parseMemPackageTests(store gno.Store, memPkg *gnovm.MemPackage) (tset, itset *gno.FileSet, itfiles, ftfiles []*gnovm.MemFile) { +func parseMemPackageTests(memPkg *gnovm.MemPackage) (tset, itset *gno.FileSet, itfiles, ftfiles []*gnovm.MemFile) { tset = &gno.FileSet{} itset = &gno.FileSet{} var errs error diff --git a/gnovm/pkg/transpiler/transpiler_test.go b/gnovm/pkg/transpiler/transpiler_test.go index 2a0707f7f79..d2b689eaf45 100644 --- a/gnovm/pkg/transpiler/transpiler_test.go +++ b/gnovm/pkg/transpiler/transpiler_test.go @@ -145,9 +145,9 @@ func hello() string { source: ` package foo -import "gno.land/r/demo/users" +import "gno.land/r/gnoland/users/v1" -func foo() { _ = users.Register} +func foo() { _ = v1.Register} `, expectedOutput: ` // Code generated by github.com/gnolang/gno. DO NOT EDIT. @@ -155,16 +155,16 @@ func foo() { _ = users.Register} //line foo.gno:1:1 package foo -import "github.com/gnolang/gno/examples/gno.land/r/demo/users" +import "github.com/gnolang/gno/examples/gno.land/r/gnoland/users/v1" -func foo() { _ = users.Register } +func foo() { _ = v1.Register } `, expectedImports: []*ast.ImportSpec{ { Path: &ast.BasicLit{ ValuePos: 21, Kind: 9, - Value: `"github.com/gnolang/gno/examples/gno.land/r/demo/users"`, + Value: `"github.com/gnolang/gno/examples/gno.land/r/gnoland/users/v1"`, }, }, }, @@ -344,13 +344,13 @@ func Float32bits(i float32) uint32 func testfunc() { println(Float32bits(3.14159)) - std.AssertOriginCall() + std.ChainID() } func otherFunc() { std := 1 // This is (incorrectly) changed for now. - std.AssertOriginCall() + std.ChainID() } `, expectedOutput: ` @@ -363,13 +363,13 @@ import "github.com/gnolang/gno/gnovm/stdlibs/std" func testfunc() { println(Float32bits(3.14159)) - std.AssertOriginCall(nil) + std.ChainID(nil) } func otherFunc() { std := 1 // This is (incorrectly) changed for now. - std.AssertOriginCall(nil) + std.ChainID(nil) } `, expectedImports: []*ast.ImportSpec{ @@ -388,12 +388,12 @@ func otherFunc() { source: ` package std -func AssertOriginCall() -func origCaller() string +func ChainID() +func originCaller() string func testfunc() { - AssertOriginCall() - println(origCaller()) + ChainID() + println(originCaller()) } `, expectedOutput: ` @@ -403,8 +403,8 @@ func testfunc() { package std func testfunc() { - AssertOriginCall(nil) - println(X_origCaller(nil)) + ChainID(nil) + println(X_originCaller(nil)) } `, }, diff --git a/gnovm/pkg/version/version.go b/gnovm/pkg/version/version.go new file mode 100644 index 00000000000..933d4fac3e5 --- /dev/null +++ b/gnovm/pkg/version/version.go @@ -0,0 +1,3 @@ +package version + +var Version = "develop" diff --git a/gnovm/stdlibs/bufio/example_test.gno b/gnovm/stdlibs/bufio/example_test.gno index d77a2686fd0..7f0fd688e48 100644 --- a/gnovm/stdlibs/bufio/example_test.gno +++ b/gnovm/stdlibs/bufio/example_test.gno @@ -24,12 +24,12 @@ func ExampleWriter() { // The simplest use of a Scanner, to read standard input as a set of lines. func ExampleScanner_lines() { - scanner := bufio.NewScanner(os.Stdin) + scanner := bufio.NewScanner(strings.NewReader("hello\nworld\n")) for scanner.Scan() { fmt.Println(scanner.Text()) // Println will add back the final '\n' } if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "reading standard input:", err) + fmt.Fprintln(os.Stderr, "reading buffer:", err) } } diff --git a/gnovm/stdlibs/bytes/reader_test.gno b/gnovm/stdlibs/bytes/reader_test.gno index a431a01e3dc..283e3273995 100644 --- a/gnovm/stdlibs/bytes/reader_test.gno +++ b/gnovm/stdlibs/bytes/reader_test.gno @@ -74,7 +74,7 @@ func TestReaderAt(t *testing.T) { off int64 n int want string - wanterr interface{} + wanterr any }{ {0, 10, "0123456789", nil}, {1, 10, "123456789", io.EOF}, diff --git a/gnovm/stdlibs/crypto/chacha20/rand/rand.gno b/gnovm/stdlibs/crypto/chacha20/rand/rand.gno index fbc3c2f1a25..059b190c9ad 100644 --- a/gnovm/stdlibs/crypto/chacha20/rand/rand.gno +++ b/gnovm/stdlibs/crypto/chacha20/rand/rand.gno @@ -191,7 +191,7 @@ func New() *RNG { // Global versions of each RNG method, leveraging a pool of RNGs. var rngpool = sync.Pool{ - New: func() interface{} { + New: func() any { return New() }, } diff --git a/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno b/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno index 615ea0eb6a7..db0cd1b9c1a 100644 --- a/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno +++ b/gnovm/stdlibs/crypto/ed25519/ed25519_test.gno @@ -1,4 +1,4 @@ -package ed25519 +package ed25519_test import ( "crypto/ed25519" diff --git a/gnovm/stdlibs/crypto/sha256/sha256_test.gno b/gnovm/stdlibs/crypto/sha256/sha256_test.gno index 26d96cd547e..21e376736a4 100644 --- a/gnovm/stdlibs/crypto/sha256/sha256_test.gno +++ b/gnovm/stdlibs/crypto/sha256/sha256_test.gno @@ -1,4 +1,4 @@ -package sha256 +package sha256_test import ( "crypto/sha256" @@ -7,7 +7,8 @@ import ( ) func TestSha256Sum(t *testing.T) { - got := sha256.Sum256([]byte("sha256 this string"))[:] + result := sha256.Sum256([]byte("sha256 this string")) + got := result[:] expected := "1af1dfa857bf1d8814fe1af8983c18080019922e557f15a8a0d3db739d77aacb" if hex.EncodeToString(got) != expected { diff --git a/gnovm/stdlibs/encoding/base32/base32_test.gno b/gnovm/stdlibs/encoding/base32/base32_test.gno index fa09c2cd2f3..3f15943feb3 100644 --- a/gnovm/stdlibs/encoding/base32/base32_test.gno +++ b/gnovm/stdlibs/encoding/base32/base32_test.gno @@ -9,7 +9,6 @@ import ( "errors" "io" "math" - "strconv" "strings" "testing" ) @@ -44,7 +43,7 @@ var bigtest = testpair{ "KR3WC4ZAMJZGS3DMNFTSYIDBNZSCA5DIMUQHG3DJORUHSIDUN53GK4Y=", } -func testEqual(t *testing.T, msg string, args ...interface{}) bool { +func testEqual(t *testing.T, msg string, args ...any) bool { t.Helper() if args[len(args)-2] != args[len(args)-1] { t.Errorf(msg, args...) @@ -219,53 +218,77 @@ func TestIssue20044(t *testing.T) { dbuflen int }{ // Check valid input data accompanied by an error is processed and the error is propagated. - {r: badReader{data: []byte("MY======"), errs: []error{badErr}}, - res: "f", err: badErr}, + { + r: badReader{data: []byte("MY======"), errs: []error{badErr}}, + res: "f", err: badErr, + }, // Check a read error accompanied by input data consisting of newlines only is propagated. - {r: badReader{data: []byte("\n\n\n\n\n\n\n\n"), errs: []error{badErr, nil}}, - res: "", err: badErr}, + { + r: badReader{data: []byte("\n\n\n\n\n\n\n\n"), errs: []error{badErr, nil}}, + res: "", err: badErr, + }, // Reader will be called twice. The first time it will return 8 newline characters. The // second time valid base32 encoded data and an error. The data should be decoded // correctly and the error should be propagated. - {r: badReader{data: []byte("\n\n\n\n\n\n\n\nMY======"), errs: []error{nil, badErr}}, - res: "f", err: badErr, dbuflen: 8}, + { + r: badReader{data: []byte("\n\n\n\n\n\n\n\nMY======"), errs: []error{nil, badErr}}, + res: "f", err: badErr, dbuflen: 8, + }, // Reader returns invalid input data (too short) and an error. Verify the reader // error is returned. - {r: badReader{data: []byte("MY====="), errs: []error{badErr}}, - res: "", err: badErr}, + { + r: badReader{data: []byte("MY====="), errs: []error{badErr}}, + res: "", err: badErr, + }, // Reader returns invalid input data (too short) but no error. Verify io.ErrUnexpectedEOF // is returned. - {r: badReader{data: []byte("MY====="), errs: []error{nil}}, - res: "", err: io.ErrUnexpectedEOF}, + { + r: badReader{data: []byte("MY====="), errs: []error{nil}}, + res: "", err: io.ErrUnexpectedEOF, + }, // Reader returns invalid input data and an error. Verify the reader and not the // decoder error is returned. - {r: badReader{data: []byte("Ma======"), errs: []error{badErr}}, - res: "", err: badErr}, + { + r: badReader{data: []byte("Ma======"), errs: []error{badErr}}, + res: "", err: badErr, + }, // Reader returns valid data and io.EOF. Check data is decoded and io.EOF is propagated. - {r: badReader{data: []byte("MZXW6YTB"), errs: []error{io.EOF}}, - res: "fooba", err: io.EOF}, + { + r: badReader{data: []byte("MZXW6YTB"), errs: []error{io.EOF}}, + res: "fooba", err: io.EOF, + }, // Check errors are properly reported when decoder.Read is called multiple times. // decoder.Read will be called 8 times, badReader.Read will be called twice, returning // valid data both times but an error on the second call. - {r: badReader{data: []byte("NRSWC43VOJSS4==="), errs: []error{nil, badErr}}, - res: "leasure.", err: badErr, dbuflen: 1}, + { + r: badReader{data: []byte("NRSWC43VOJSS4==="), errs: []error{nil, badErr}}, + res: "leasure.", err: badErr, dbuflen: 1, + }, // Check io.EOF is properly reported when decoder.Read is called multiple times. // decoder.Read will be called 8 times, badReader.Read will be called twice, returning // valid data both times but io.EOF on the second call. - {r: badReader{data: []byte("NRSWC43VOJSS4==="), errs: []error{nil, io.EOF}}, - res: "leasure.", err: io.EOF, dbuflen: 1}, + { + r: badReader{data: []byte("NRSWC43VOJSS4==="), errs: []error{nil, io.EOF}}, + res: "leasure.", err: io.EOF, dbuflen: 1, + }, // The following two test cases check that errors are propagated correctly when more than // 8 bytes are read at a time. - {r: badReader{data: []byte("NRSWC43VOJSS4==="), errs: []error{io.EOF}}, - res: "leasure.", err: io.EOF, dbuflen: 11}, - {r: badReader{data: []byte("NRSWC43VOJSS4==="), errs: []error{badErr}}, - res: "leasure.", err: badErr, dbuflen: 11}, + { + r: badReader{data: []byte("NRSWC43VOJSS4==="), errs: []error{io.EOF}}, + res: "leasure.", err: io.EOF, dbuflen: 11, + }, + { + r: badReader{data: []byte("NRSWC43VOJSS4==="), errs: []error{badErr}}, + res: "leasure.", err: badErr, dbuflen: 11, + }, // Check that errors are correctly propagated when the reader returns valid bytes in // groups that are not divisible by 8. The first read will return 11 bytes and no // error. The second will return 7 and an error. The data should be decoded correctly // and the error should be propagated. - {r: badReader{data: []byte("NRSWC43VOJSS4==="), errs: []error{nil, badErr}, limit: 11}, - res: "leasure.", err: badErr}, + { + r: badReader{data: []byte("NRSWC43VOJSS4==="), errs: []error{nil, badErr}, limit: 11}, + res: "leasure.", err: badErr, + }, } for _, tc := range testCases { @@ -739,7 +762,7 @@ func TestBufferedDecodingPadding(t *testing.T) { } func TestEncodedLen(t *testing.T) { - var rawStdEncoding = StdEncoding.WithPadding(NoPadding) + rawStdEncoding := StdEncoding.WithPadding(NoPadding) type test struct { enc *Encoding n int @@ -765,15 +788,9 @@ func TestEncodedLen(t *testing.T) { {rawStdEncoding, 7, 12}, {rawStdEncoding, 10, 16}, {rawStdEncoding, 11, 18}, - } - // check overflow - switch strconv.IntSize { - case 32: - tests = append(tests, test{rawStdEncoding, (math.MaxInt-4)/8 + 1, 429496730}) - tests = append(tests, test{rawStdEncoding, math.MaxInt/8*5 + 4, math.MaxInt}) - case 64: - tests = append(tests, test{rawStdEncoding, (math.MaxInt-4)/8 + 1, 1844674407370955162}) - tests = append(tests, test{rawStdEncoding, math.MaxInt/8*5 + 4, math.MaxInt}) + // check overflow + {rawStdEncoding, (math.MaxInt-4)/8 + 1, 1844674407370955162}, + {rawStdEncoding, math.MaxInt/8*5 + 4, math.MaxInt}, } for _, tt := range tests { if got := tt.enc.EncodedLen(tt.n); int64(got) != tt.want { @@ -783,7 +800,7 @@ func TestEncodedLen(t *testing.T) { } func TestDecodedLen(t *testing.T) { - var rawStdEncoding = StdEncoding.WithPadding(NoPadding) + rawStdEncoding := StdEncoding.WithPadding(NoPadding) type test struct { enc *Encoding n int @@ -804,15 +821,9 @@ func TestDecodedLen(t *testing.T) { {rawStdEncoding, 12, 7}, {rawStdEncoding, 16, 10}, {rawStdEncoding, 18, 11}, - } - // check overflow - switch strconv.IntSize { - case 32: - tests = append(tests, test{rawStdEncoding, math.MaxInt/5 + 1, 268435456}) - tests = append(tests, test{rawStdEncoding, math.MaxInt, 1342177279}) - case 64: - tests = append(tests, test{rawStdEncoding, math.MaxInt/5 + 1, 1152921504606846976}) - tests = append(tests, test{rawStdEncoding, math.MaxInt, 5764607523034234879}) + // check overflow + {rawStdEncoding, math.MaxInt/5 + 1, 1152921504606846976}, + {rawStdEncoding, math.MaxInt, 5764607523034234879}, } for _, tt := range tests { if got := tt.enc.DecodedLen(tt.n); int64(got) != tt.want { diff --git a/gnovm/stdlibs/encoding/base64/base64_test.gno b/gnovm/stdlibs/encoding/base64/base64_test.gno index bfaa9b340f6..090d477f165 100644 --- a/gnovm/stdlibs/encoding/base64/base64_test.gno +++ b/gnovm/stdlibs/encoding/base64/base64_test.gno @@ -94,7 +94,7 @@ var bigtest = testpair{ "VHdhcyBicmlsbGlnLCBhbmQgdGhlIHNsaXRoeSB0b3Zlcw==", } -func testEqual(t *testing.T, msg string, args ...interface{}) bool { +func testEqual(t *testing.T, msg string, args ...any) bool { t.Helper() if args[len(args)-2] != args[len(args)-1] { t.Errorf(msg, args...) diff --git a/gnovm/stdlibs/encoding/binary/binary_test.gno b/gnovm/stdlibs/encoding/binary/binary_test.gno index 5407eb5061b..7905c381189 100644 --- a/gnovm/stdlibs/encoding/binary/binary_test.gno +++ b/gnovm/stdlibs/encoding/binary/binary_test.gno @@ -1,4 +1,4 @@ -package binary +package binary_test import ( "bytes" diff --git a/gnovm/stdlibs/encoding/csv/reader_test.gno b/gnovm/stdlibs/encoding/csv/reader_test.gno index fa4ecdce3ed..07749bdd8b6 100644 --- a/gnovm/stdlibs/encoding/csv/reader_test.gno +++ b/gnovm/stdlibs/encoding/csv/reader_test.gno @@ -500,7 +500,7 @@ func isErr(err, match error) bool { } // XXX: substitute for reflect.DeepEqual -func deepEqual(v, x interface{}) bool { +func deepEqual(v, x any) bool { if v == nil { return x == nil } diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index ab35fc6b6bf..782cef14c6c 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -1,4 +1,4 @@ -// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. +// Code generated by the genstd tool (@/misc/genstd); DO NOT EDIT. // To regenerate it, run `go generate` from this directory. package stdlibs @@ -11,6 +11,7 @@ import ( 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_sys_params "github.com/gnolang/gno/gnovm/stdlibs/sys/params" libs_testing "github.com/gnolang/gno/gnovm/stdlibs/testing" libs_time "github.com/gnolang/gno/gnovm/stdlibs/time" ) @@ -57,9 +58,15 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) r0 := libs_crypto_ed25519.X_verify(p0, p1, p2) @@ -87,7 +94,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := libs_crypto_sha256.X_sum256(p0) @@ -115,7 +124,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := libs_math.Float32bits(p0) @@ -143,7 +154,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := libs_math.Float32frombits(p0) @@ -171,7 +184,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := libs_math.Float64bits(p0) @@ -199,7 +214,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := libs_math.Float64frombits(p0) @@ -231,8 +248,12 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) r0, r1 := libs_std.X_bankerGetCoins( m, @@ -277,11 +298,21 @@ var nativeFuncs = [...]NativeFunc{ rp4 = reflect.ValueOf(&p4).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) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 4, "")).TV, rp4) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) + tv3 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV + tv3.DeepFill(m.Store) + gno.Gno2GoValue(tv3, rp3) + tv4 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 4, "")).TV + tv4.DeepFill(m.Store) + gno.Gno2GoValue(tv4, rp4) libs_std.X_bankerSendCoins( m, @@ -308,8 +339,12 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) r0 := libs_std.X_bankerTotalCoin( m, @@ -346,10 +381,18 @@ var nativeFuncs = [...]NativeFunc{ rp3 = reflect.ValueOf(&p3).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) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) + tv3 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV + tv3.DeepFill(m.Store) + gno.Gno2GoValue(tv3, rp3) libs_std.X_bankerIssueCoin( m, @@ -380,10 +423,18 @@ var nativeFuncs = [...]NativeFunc{ rp3 = reflect.ValueOf(&p3).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) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) + tv3 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV + tv3.DeepFill(m.Store) + gno.Gno2GoValue(tv3, rp3) libs_std.X_bankerRemoveCoin( m, @@ -408,8 +459,12 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) libs_std.X_emit( m, @@ -430,34 +485,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "IsOriginCall", - []gno.FieldTypeExpr{}, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("bool")}, - }, - true, - func(m *gno.Machine) { - r0 := libs_std.IsOriginCall( - m, - ) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - }, - }, - { - "std", - "GetChainID", + "ChainID", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, true, func(m *gno.Machine) { - r0 := libs_std.GetChainID( + r0 := libs_std.ChainID( m, ) @@ -470,14 +505,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "GetChainDomain", + "ChainDomain", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, true, func(m *gno.Machine) { - r0 := libs_std.GetChainDomain( + r0 := libs_std.ChainDomain( m, ) @@ -490,14 +525,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "GetHeight", + "ChainHeight", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, true, func(m *gno.Machine) { - r0 := libs_std.GetHeight( + r0 := libs_std.ChainHeight( m, ) @@ -510,7 +545,7 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "origSend", + "originSend", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("[]string")}, @@ -518,7 +553,7 @@ var nativeFuncs = [...]NativeFunc{ }, true, func(m *gno.Machine) { - r0, r1 := libs_std.X_origSend( + r0, r1 := libs_std.X_originSend( m, ) @@ -536,14 +571,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "origCaller", + "originCaller", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, true, func(m *gno.Machine) { - r0 := libs_std.X_origCaller( + r0 := libs_std.X_originCaller( m, ) @@ -556,14 +591,14 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "origPkgAddr", + "originPkgAddr", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, true, func(m *gno.Machine) { - r0 := libs_std.X_origPkgAddr( + r0 := libs_std.X_originPkgAddr( m, ) @@ -591,7 +626,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := libs_std.X_callerAt( m, @@ -622,7 +659,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0, r1 := libs_std.X_getRealm( m, @@ -670,8 +709,12 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) libs_std.X_setParamString( m, @@ -696,8 +739,12 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) libs_std.X_setParamBool( m, @@ -722,8 +769,12 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) libs_std.X_setParamInt64( m, @@ -748,8 +799,12 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) libs_std.X_setParamUint64( m, @@ -774,14 +829,300 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) libs_std.X_setParamBytes( m, p0, p1) }, }, + { + "std", + "setParamStrings", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("[]string")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 []string + rp1 = reflect.ValueOf(&p1).Elem() + ) + + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + + libs_std.X_setParamStrings( + m, + p0, p1) + }, + }, + { + "sys/params", + "setSysParamString", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 string + rp3 = reflect.ValueOf(&p3).Elem() + ) + + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) + tv3 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV + tv3.DeepFill(m.Store) + gno.Gno2GoValue(tv3, rp3) + + libs_sys_params.X_setSysParamString( + m, + p0, p1, p2, p3) + }, + }, + { + "sys/params", + "setSysParamBool", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("bool")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 bool + rp3 = reflect.ValueOf(&p3).Elem() + ) + + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) + tv3 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV + tv3.DeepFill(m.Store) + gno.Gno2GoValue(tv3, rp3) + + libs_sys_params.X_setSysParamBool( + m, + p0, p1, p2, p3) + }, + }, + { + "sys/params", + "setSysParamInt64", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("int64")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 int64 + rp3 = reflect.ValueOf(&p3).Elem() + ) + + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) + tv3 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV + tv3.DeepFill(m.Store) + gno.Gno2GoValue(tv3, rp3) + + libs_sys_params.X_setSysParamInt64( + m, + p0, p1, p2, p3) + }, + }, + { + "sys/params", + "setSysParamUint64", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("uint64")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 uint64 + rp3 = reflect.ValueOf(&p3).Elem() + ) + + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) + tv3 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV + tv3.DeepFill(m.Store) + gno.Gno2GoValue(tv3, rp3) + + libs_sys_params.X_setSysParamUint64( + m, + p0, p1, p2, p3) + }, + }, + { + "sys/params", + "setSysParamBytes", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("[]byte")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 []byte + rp3 = reflect.ValueOf(&p3).Elem() + ) + + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) + tv3 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV + tv3.DeepFill(m.Store) + gno.Gno2GoValue(tv3, rp3) + + libs_sys_params.X_setSysParamBytes( + m, + p0, p1, p2, p3) + }, + }, + { + "sys/params", + "setSysParamStrings", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("[]string")}, + }, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 []string + rp3 = reflect.ValueOf(&p3).Elem() + ) + + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) + tv3 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV + tv3.DeepFill(m.Store) + gno.Gno2GoValue(tv3, rp3) + + libs_sys_params.X_setSysParamStrings( + m, + p0, p1, p2, p3) + }, + }, { "testing", "unixNano", @@ -800,6 +1141,68 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, + { + "testing", + "recoverWithStacktrace", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.AnyT()}, + {Name: gno.N("r1"), Type: gno.X("string")}, + }, + false, + func(m *gno.Machine) { + r0, r1 := libs_testing.X_recoverWithStacktrace() + + m.PushValue(r0) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, + { + "testing", + "matchString", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("r1"), Type: gno.X("error")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + ) + + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + + r0, r1 := libs_testing.X_matchString(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, { "time", "now", @@ -850,7 +1253,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0, r1 := libs_time.X_loadFromEmbeddedTZData(p0) @@ -904,6 +1309,7 @@ var initOrder = [...]string{ "regexp/syntax", "regexp", "std", + "sys/params", "testing", "time", "unicode/utf16", diff --git a/gnovm/stdlibs/hash/marshal_test.gno b/gnovm/stdlibs/hash/marshal_test.gno index bf823d97cce..d15c02e87b5 100644 --- a/gnovm/stdlibs/hash/marshal_test.gno +++ b/gnovm/stdlibs/hash/marshal_test.gno @@ -6,7 +6,7 @@ // BinaryMarshaler, BinaryUnmarshaler, // and lock in the current representations. -package hash +package hash_test import ( "bytes" diff --git a/gnovm/stdlibs/io/export_test.gno b/gnovm/stdlibs/io/export_test.gno new file mode 100644 index 00000000000..06853f975f5 --- /dev/null +++ b/gnovm/stdlibs/io/export_test.gno @@ -0,0 +1,10 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package io + +// exported for test +var ErrInvalidWrite = errInvalidWrite +var ErrWhence = errWhence +var ErrOffset = errOffset diff --git a/gnovm/stdlibs/io/io.gno b/gnovm/stdlibs/io/io.gno index bdbab135140..4b1abf6d80a 100644 --- a/gnovm/stdlibs/io/io.gno +++ b/gnovm/stdlibs/io/io.gno @@ -654,7 +654,7 @@ func (discard) WriteString(s string) (int, error) { /* var blackHolePool = sync.Pool{ - New: func() interface{} { + New: func() any { b := make([]byte, 8192) return &b }, diff --git a/gnovm/stdlibs/io/io_test.gno b/gnovm/stdlibs/io/io_test.gno index 4915982057b..38e535b3cfb 100644 --- a/gnovm/stdlibs/io/io_test.gno +++ b/gnovm/stdlibs/io/io_test.gno @@ -1,4 +1,4 @@ -package io +package io_test // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -8,6 +8,7 @@ import ( "bytes" "errors" "fmt" + "io" "strings" "testing" ) @@ -15,8 +16,8 @@ import ( // A version of bytes.Buffer without ReadFrom and WriteTo type Buffer struct { bytes.Buffer - ReaderFrom // conflicts with and hides bytes.Buffer's ReaderFrom. - WriterTo // conflicts with and hides bytes.Buffer's WriterTo. + io.ReaderFrom // conflicts with and hides bytes.Buffer's ReaderFrom. + io.WriterTo // conflicts with and hides bytes.Buffer's WriterTo. } // Simple tests, primarily to verify the ReadFrom and WriteTo callouts inside Copy, CopyBuffer and CopyN. @@ -25,7 +26,7 @@ func TestCopy(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } @@ -35,12 +36,12 @@ func TestCopyNegative(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello") - Copy(wb, &LimitedReader{R: rb, N: -1}) + io.Copy(wb, &io.LimitedReader{R: rb, N: -1}) if wb.String() != "" { t.Errorf("Copy on LimitedReader with N<0 copied data") } - CopyN(wb, rb, -1) + io.CopyN(wb, rb, -1) if wb.String() != "" { t.Errorf("CopyN with N<0 copied data") } @@ -50,7 +51,7 @@ func TestCopyBuffer(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - CopyBuffer(wb, rb, make([]byte, 1)) // Tiny buffer to keep it honest. + io.CopyBuffer(wb, rb, make([]byte, 1)) // Tiny buffer to keep it honest. if wb.String() != "hello, world." { t.Errorf("CopyBuffer did not work properly") } @@ -60,7 +61,7 @@ func TestCopyBufferNil(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - CopyBuffer(wb, rb, nil) // Should allocate a buffer. + io.CopyBuffer(wb, rb, nil) // Should allocate a buffer. if wb.String() != "hello, world." { t.Errorf("CopyBuffer did not work properly") } @@ -70,7 +71,7 @@ func TestCopyReadFrom(t *testing.T) { rb := new(Buffer) wb := new(bytes.Buffer) // implements ReadFrom. rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } @@ -80,7 +81,7 @@ func TestCopyWriteTo(t *testing.T) { rb := new(bytes.Buffer) // implements WriteTo. wb := new(Buffer) rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } @@ -92,7 +93,7 @@ type writeToChecker struct { writeToCalled bool } -func (wt *writeToChecker) WriteTo(w Writer) (int64, error) { +func (wt *writeToChecker) WriteTo(w io.Writer) (int64, error) { wt.writeToCalled = true return wt.Buffer.WriteTo(w) } @@ -104,7 +105,7 @@ func TestCopyPriority(t *testing.T) { rb := new(writeToChecker) wb := new(bytes.Buffer) rb.WriteString("hello, world.") - Copy(wb, rb) + io.Copy(wb, rb) if wb.String() != "hello, world." { t.Errorf("Copy did not work properly") } else if !rb.writeToCalled { @@ -134,7 +135,7 @@ func (w errWriter) Write([]byte) (int, error) { func TestCopyReadErrWriteErr(t *testing.T) { er, ew := errors.New("readError"), errors.New("writeError") r, w := zeroErrReader{err: er}, errWriter{err: ew} - n, err := Copy(w, r) + n, err := io.Copy(w, r) if n != 0 || err != ew { t.Errorf("Copy(zeroErrReader, errWriter) = %d, %v; want 0, writeError", n, err) } @@ -144,7 +145,7 @@ func TestCopyN(t *testing.T) { rb := new(Buffer) wb := new(Buffer) rb.WriteString("hello, world.") - CopyN(wb, rb, 5) + io.CopyN(wb, rb, 5) if wb.String() != "hello" { t.Errorf("CopyN did not work properly") } @@ -154,7 +155,7 @@ func TestCopyNReadFrom(t *testing.T) { rb := new(Buffer) wb := new(bytes.Buffer) // implements ReadFrom. rb.WriteString("hello") - CopyN(wb, rb, 5) + io.CopyN(wb, rb, 5) if wb.String() != "hello" { t.Errorf("CopyN did not work properly") } @@ -164,7 +165,7 @@ func TestCopyNWriteTo(t *testing.T) { rb := new(bytes.Buffer) // implements WriteTo. wb := new(Buffer) rb.WriteString("hello, world.") - CopyN(wb, rb, 5) + io.CopyN(wb, rb, 5) if wb.String() != "hello" { t.Errorf("CopyN did not work properly") } @@ -177,7 +178,7 @@ func BenchmarkCopyNSmall(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - CopyN(buf, rd, 512) + io.CopyN(buf, rd, 512) rd.Reset(bs) } } @@ -189,13 +190,13 @@ func BenchmarkCopyNLarge(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - CopyN(buf, rd, 32*1024) + io.CopyN(buf, rd, 32*1024) rd.Reset(bs) } } type noReadFrom struct { - w Writer + w io.Writer } func (w *noReadFrom) Write(p []byte) (n int, err error) { @@ -214,32 +215,32 @@ func TestCopyNEOF(t *testing.T) { b := new(bytes.Buffer) - n, err := CopyN(&noReadFrom{b}, strings.NewReader("foo"), 3) + n, err := io.CopyN(&noReadFrom{b}, strings.NewReader("foo"), 3) if n != 3 || err != nil { t.Errorf("CopyN(noReadFrom, foo, 3) = %d, %v; want 3, nil", n, err) } - n, err = CopyN(&noReadFrom{b}, strings.NewReader("foo"), 4) - if n != 3 || err != EOF { + n, err = io.CopyN(&noReadFrom{b}, strings.NewReader("foo"), 4) + if n != 3 || err != io.EOF { t.Errorf("CopyN(noReadFrom, foo, 4) = %d, %v; want 3, EOF", n, err) } - n, err = CopyN(b, strings.NewReader("foo"), 3) // b has read from + n, err = io.CopyN(b, strings.NewReader("foo"), 3) // b has read from if n != 3 || err != nil { t.Errorf("CopyN(bytes.Buffer, foo, 3) = %d, %v; want 3, nil", n, err) } - n, err = CopyN(b, strings.NewReader("foo"), 4) // b has read from - if n != 3 || err != EOF { + n, err = io.CopyN(b, strings.NewReader("foo"), 4) // b has read from + if n != 3 || err != io.EOF { t.Errorf("CopyN(bytes.Buffer, foo, 4) = %d, %v; want 3, EOF", n, err) } - n, err = CopyN(b, wantedAndErrReader{}, 5) + n, err = io.CopyN(b, wantedAndErrReader{}, 5) if n != 5 || err != nil { t.Errorf("CopyN(bytes.Buffer, wantedAndErrReader, 5) = %d, %v; want 5, nil", n, err) } - n, err = CopyN(&noReadFrom{b}, wantedAndErrReader{}, 5) + n, err = io.CopyN(&noReadFrom{b}, wantedAndErrReader{}, 5) if n != 5 || err != nil { t.Errorf("CopyN(noReadFrom, wantedAndErrReader, 5) = %d, %v; want 5, nil", n, err) } @@ -267,7 +268,7 @@ func (r *dataAndErrorBuffer) Read(p []byte) (n int, err error) { func TestReadAtLeastWithDataAndEOF(t *testing.T) { var rb dataAndErrorBuffer - rb.err = EOF + rb.err = io.EOF testReadAtLeast(t, &rb) } @@ -277,41 +278,41 @@ func TestReadAtLeastWithDataAndError(t *testing.T) { testReadAtLeast(t, &rb) } -func testReadAtLeast(t *testing.T, rb ReadWriter) { +func testReadAtLeast(t *testing.T, rb io.ReadWriter) { rb.Write([]byte("0123")) buf := make([]byte, 2) - n, err := ReadAtLeast(rb, buf, 2) + n, err := io.ReadAtLeast(rb, buf, 2) if err != nil { t.Error(err) } if n != 2 { t.Errorf("expected to have read 2 bytes, got %v", n) } - n, err = ReadAtLeast(rb, buf, 4) - if err != ErrShortBuffer { + n, err = io.ReadAtLeast(rb, buf, 4) + if err != io.ErrShortBuffer { t.Errorf("expected `ErrShortBuffer` got %v", err) } if n != 0 { t.Errorf("expected to have read 0 bytes, got %v", n) } - n, err = ReadAtLeast(rb, buf, 1) + n, err = io.ReadAtLeast(rb, buf, 1) if err != nil { t.Error(err) } if n != 2 { t.Errorf("expected to have read 2 bytes, got %v", n) } - n, err = ReadAtLeast(rb, buf, 2) - if err != EOF { + n, err = io.ReadAtLeast(rb, buf, 2) + if err != io.EOF { t.Errorf("expected EOF, got %v", err) } if n != 0 { t.Errorf("expected to have read 0 bytes, got %v", n) } rb.Write([]byte("4")) - n, err = ReadAtLeast(rb, buf, 2) - want := ErrUnexpectedEOF - if rb, ok := rb.(*dataAndErrorBuffer); ok && rb.err != EOF { + n, err = io.ReadAtLeast(rb, buf, 2) + want := io.ErrUnexpectedEOF + if rb, ok := rb.(*dataAndErrorBuffer); ok && rb.err != io.EOF { want = rb.err } if err != want { @@ -338,7 +339,7 @@ func TestTeeReader(t *testing.T) { if !bytes.Equal(wb.Bytes(), src) { t.Errorf("bytes written = %q want %q", wb.Bytes(), src) } - if n, err := r.Read(dst); n != 0 || err != EOF { + if n, err := r.Read(dst); n != 0 || err != io.EOF { t.Errorf("r.Read at EOF = %d, %v want 0, EOF", n, err) } rb = bytes.NewBuffer(src) @@ -362,22 +363,22 @@ func TestSectionReader_ReadAt(t *testing.T) { exp string err error }{ - {data: "", off: 0, n: 10, bufLen: 2, at: 0, exp: "", err: EOF}, + {data: "", off: 0, n: 10, bufLen: 2, at: 0, exp: "", err: io.EOF}, {data: dat, off: 0, n: len(dat), bufLen: 0, at: 0, exp: "", err: nil}, - {data: dat, off: len(dat), n: 1, bufLen: 1, at: 0, exp: "", err: EOF}, + {data: dat, off: len(dat), n: 1, bufLen: 1, at: 0, exp: "", err: io.EOF}, {data: dat, off: 0, n: len(dat) + 2, bufLen: len(dat), at: 0, exp: dat, err: nil}, {data: dat, off: 0, n: len(dat), bufLen: len(dat) / 2, at: 0, exp: dat[:len(dat)/2], err: nil}, {data: dat, off: 0, n: len(dat), bufLen: len(dat), at: 0, exp: dat, err: nil}, {data: dat, off: 0, n: len(dat), bufLen: len(dat) / 2, at: 2, exp: dat[2 : 2+len(dat)/2], err: nil}, {data: dat, off: 3, n: len(dat), bufLen: len(dat) / 2, at: 2, exp: dat[5 : 5+len(dat)/2], err: nil}, {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 - 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: nil}, - {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 + 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: EOF}, - {data: dat, off: 0, n: 0, bufLen: 0, at: -1, exp: "", err: EOF}, - {data: dat, off: 0, n: 0, bufLen: 0, at: 1, exp: "", err: EOF}, + {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 + 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: io.EOF}, + {data: dat, off: 0, n: 0, bufLen: 0, at: -1, exp: "", err: io.EOF}, + {data: dat, off: 0, n: 0, bufLen: 0, at: 1, exp: "", err: io.EOF}, } for i, tt := range tests { r := strings.NewReader(tt.data) - s := NewSectionReader(r, int64(tt.off), int64(tt.n)) + s := io.NewSectionReader(r, int64(tt.off), int64(tt.n)) buf := make([]byte, tt.bufLen) if n, err := s.ReadAt(buf, int64(tt.at)); n != len(tt.exp) || string(buf[:n]) != tt.exp || err != tt.err { t.Fatalf("%d: ReadAt(%d) = %q, %v; expected %q, %v", i, tt.at, buf[:n], err, tt.exp, tt.err) @@ -388,9 +389,9 @@ func TestSectionReader_ReadAt(t *testing.T) { func TestSectionReader_Seek(t *testing.T) { // Verifies that NewSectionReader's Seeker behaves like bytes.NewReader (which is like strings.NewReader) br := bytes.NewReader([]byte("foo")) - sr := NewSectionReader(br, 0, int64(len("foo"))) + sr := io.NewSectionReader(br, 0, int64(len("foo"))) - for _, whence := range []int{SeekStart, SeekCurrent, SeekEnd} { + for _, whence := range []int{io.SeekStart, io.SeekCurrent, io.SeekEnd} { for offset := int64(-3); offset <= 4; offset++ { brOff, brErr := br.Seek(offset, whence) srOff, srErr := sr.Seek(offset, whence) @@ -402,13 +403,13 @@ func TestSectionReader_Seek(t *testing.T) { } // And verify we can just seek past the end and get an EOF - got, err := sr.Seek(100, SeekStart) + got, err := sr.Seek(100, io.SeekStart) if err != nil || got != 100 { t.Errorf("Seek = %v, %v; want 100, nil", got, err) } n, err := sr.Read(make([]byte, 10)) - if n != 0 || err != EOF { + if n != 0 || err != io.EOF { t.Errorf("Read = %v, %v; want 0, EOF", n, err) } } @@ -424,7 +425,7 @@ func TestSectionReader_Size(t *testing.T) { for _, tt := range tests { r := strings.NewReader(tt.data) - sr := NewSectionReader(r, 0, int64(len(tt.data))) + sr := io.NewSectionReader(r, 0, int64(len(tt.data))) if got := sr.Size(); got != tt.want { t.Errorf("Size = %v; want %v", got, tt.want) } @@ -442,11 +443,11 @@ func (w largeWriter) Write(p []byte) (int, error) { } func TestCopyLargeWriter(t *testing.T) { - want := errInvalidWrite + want := io.ErrInvalidWrite rb := new(Buffer) wb := largeWriter{} rb.WriteString("hello, world.") - if _, err := Copy(wb, rb); err != want { + if _, err := io.Copy(wb, rb); err != want { t.Errorf("Copy error: got %v, want %v", err, want) } @@ -454,7 +455,7 @@ func TestCopyLargeWriter(t *testing.T) { rb = new(Buffer) wb = largeWriter{err: want} rb.WriteString("hello, world.") - if _, err := Copy(wb, rb); err != want { + if _, err := io.Copy(wb, rb); err != want { t.Errorf("Copy error: got %v, want %v", err, want) } } @@ -462,18 +463,18 @@ func TestCopyLargeWriter(t *testing.T) { func TestNopCloserWriterToForwarding(t *testing.T) { for _, tc := range [...]struct { Name string - r Reader + r io.Reader }{ - {"not a WriterTo", Reader(nil)}, + {"not a WriterTo", io.Reader(nil)}, {"a WriterTo", struct { - Reader - WriterTo + io.Reader + io.WriterTo }{}}, } { - nc := NopCloser(tc.r) + nc := io.NopCloser(tc.r) - _, expected := tc.r.(WriterTo) - _, got := nc.(WriterTo) + _, expected := tc.r.(io.WriterTo) + _, got := nc.(io.WriterTo) if expected != got { t.Errorf("NopCloser incorrectly forwards WriterTo for %s, got %t want %t", tc.Name, got, expected) } @@ -488,28 +489,28 @@ func TestNopCloserWriterToForwarding(t *testing.T) { // t.Fatalf("CreateTemp(%s) failed: %v", tmpfilename, err) // } // defer tmpfile.Close() -// w := NewOffsetWriter(tmpfile, 0) +// w := io.NewOffsetWriter(tmpfile, 0) // // Should throw error errWhence if whence is not valid // t.Run("errWhence", func(t *testing.T) { // for _, whence := range []int{-3, -2, -1, 3, 4, 5} { // var offset int64 = 0 // gotOff, gotErr := w.Seek(offset, whence) -// if gotOff != 0 || gotErr != errWhence { +// if gotOff != 0 || gotErr != io.ErrWhence { // t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", -// whence, offset, gotOff, gotErr, 0, errWhence) +// whence, offset, gotOff, gotErr, 0, io.ErrWhence) // } // } // }) // // Should throw error errOffset if offset is negative // t.Run("errOffset", func(t *testing.T) { -// for _, whence := range []int{SeekStart, SeekCurrent} { +// for _, whence := range []int{io.SeekStart, io.SeekCurrent} { // for offset := int64(-3); offset < 0; offset++ { // gotOff, gotErr := w.Seek(offset, whence) -// if gotOff != 0 || gotErr != errOffset { +// if gotOff != 0 || gotErr != io.ErrOffset { // t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", -// whence, offset, gotOff, gotErr, 0, ErrOffset) +// whence, offset, gotOff, gotErr, 0, io.ErrOffset) // } // } // } @@ -523,12 +524,12 @@ func TestNopCloserWriterToForwarding(t *testing.T) { // returnOff int64 // }{ // // keep in order -// {whence: SeekStart, offset: 1, returnOff: 1}, -// {whence: SeekStart, offset: 2, returnOff: 2}, -// {whence: SeekStart, offset: 3, returnOff: 3}, -// {whence: SeekCurrent, offset: 1, returnOff: 4}, -// {whence: SeekCurrent, offset: 2, returnOff: 6}, -// {whence: SeekCurrent, offset: 3, returnOff: 9}, +// {whence: io.SeekStart, offset: 1, returnOff: 1}, +// {whence: io.SeekStart, offset: 2, returnOff: 2}, +// {whence: io.SeekStart, offset: 3, returnOff: 3}, +// {whence: io.SeekCurrent, offset: 1, returnOff: 4}, +// {whence: io.SeekCurrent, offset: 2, returnOff: 6}, +// {whence: io.SeekCurrent, offset: 3, returnOff: 9}, // } // for idx, tt := range tests { // gotOff, gotErr := w.Seek(tt.offset, tt.whence) @@ -546,28 +547,28 @@ func TestNopCloserWriterToForwarding(t *testing.T) { // to use the original approach instead of this method. (just un-comment the test above) func TestOffsetWriter_Seek(t *testing.T) { buf := new(bytes.Buffer) - w := NewOffsetWriter(testWriterAt{buf}, 0) + w := io.NewOffsetWriter(testWriterAt{buf}, 0) // Should throw error errWhence if whence is not valid t.Run("errWhence", func(t *testing.T) { for _, whence := range []int{-3, -2, -1, 3, 4, 5} { var offset int64 = 0 gotOff, gotErr := w.Seek(offset, whence) - if gotOff != 0 || gotErr != errWhence { + if gotOff != 0 || gotErr != io.ErrWhence { t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", - whence, offset, gotOff, gotErr, 0, errWhence) + whence, offset, gotOff, gotErr, 0, io.ErrWhence) } } }) // Should throw error errOffset if offset is negative t.Run("errOffset", func(t *testing.T) { - for _, whence := range []int{SeekStart, SeekCurrent} { + for _, whence := range []int{io.SeekStart, io.SeekCurrent} { for offset := int64(-3); offset < 0; offset++ { gotOff, gotErr := w.Seek(offset, whence) - if gotOff != 0 || gotErr != errOffset { + if gotOff != 0 || gotErr != io.ErrOffset { t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)", - whence, offset, gotOff, gotErr, 0, errOffset) + whence, offset, gotOff, gotErr, 0, io.ErrOffset) } } } @@ -579,12 +580,12 @@ func TestOffsetWriter_Seek(t *testing.T) { whence int returnOff int64 }{ - {whence: SeekStart, offset: 1, returnOff: 1}, - {whence: SeekStart, offset: 2, returnOff: 2}, - {whence: SeekStart, offset: 3, returnOff: 3}, - {whence: SeekCurrent, offset: 1, returnOff: 4}, - {whence: SeekCurrent, offset: 2, returnOff: 6}, - {whence: SeekCurrent, offset: 3, returnOff: 9}, + {whence: io.SeekStart, offset: 1, returnOff: 1}, + {whence: io.SeekStart, offset: 2, returnOff: 2}, + {whence: io.SeekStart, offset: 3, returnOff: 3}, + {whence: io.SeekCurrent, offset: 1, returnOff: 4}, + {whence: io.SeekCurrent, offset: 2, returnOff: 6}, + {whence: io.SeekCurrent, offset: 3, returnOff: 9}, } for idx, tt := range tests { gotOff, gotErr := w.Seek(tt.offset, tt.whence) diff --git a/gnovm/stdlibs/io/multi_test.gno b/gnovm/stdlibs/io/multi_test.gno index f39895ea776..908dffd3cce 100644 --- a/gnovm/stdlibs/io/multi_test.gno +++ b/gnovm/stdlibs/io/multi_test.gno @@ -1,4 +1,4 @@ -package io +package io_test // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -8,6 +8,7 @@ import ( "bytes" "crypto/sha256" "fmt" + "io" "strings" "testing" ) @@ -17,14 +18,14 @@ type Stringer interface { } func TestMultiReader(t *testing.T) { - var mr Reader + var mr io.Reader var buf []byte nread := 0 withFooBar := func(tests func()) { r1 := strings.NewReader("foo ") r2 := strings.NewReader("") r3 := strings.NewReader("bar") - mr = MultiReader(r1, r2, r3) + mr = io.MultiReader(r1, r2, r3) buf = make([]byte, 20) tests() } @@ -50,13 +51,13 @@ func TestMultiReader(t *testing.T) { expectRead(2, "fo", nil) expectRead(5, "o ", nil) expectRead(5, "bar", nil) - expectRead(5, "", EOF) + expectRead(5, "", io.EOF) }) withFooBar(func() { expectRead(4, "foo ", nil) expectRead(1, "b", nil) expectRead(3, "ar", nil) - expectRead(1, "", EOF) + expectRead(1, "", io.EOF) }) withFooBar(func() { expectRead(5, "foo ", nil) @@ -67,7 +68,7 @@ func TestMultiWriter(t *testing.T) { sink := new(bytes.Buffer) // Hide bytes.Buffer's WriteString method: testMultiWriter(t, struct { - Writer + io.Writer Stringer }{sink, sink}) } @@ -82,11 +83,11 @@ func TestMultiWriter_String(t *testing.T) { func TestMultiWriter_WriteStringSingleAlloc(t *testing.T) { var sink1, sink2 bytes.Buffer type simpleWriter struct { // hide bytes.Buffer's WriteString - Writer + io.Writer } - mw := MultiWriter(simpleWriter{&sink1}, simpleWriter{&sink2}) + mw := io.MultiWriter(simpleWriter{&sink1}, simpleWriter{&sink2}) allocs := int(testing.AllocsPerRun2(1000, func() { - WriteString(mw, "foo") + io.WriteString(mw, "foo") })) if allocs != 1 { t.Errorf("num allocations = %d; want 1", allocs) @@ -107,24 +108,24 @@ func (c *writeStringChecker) Write(p []byte) (n int, err error) { func TestMultiWriter_StringCheckCall(t *testing.T) { var c writeStringChecker - mw := MultiWriter(&c) - WriteString(mw, "foo") + mw := io.MultiWriter(&c) + io.WriteString(mw, "foo") if !c.called { t.Error("did not see WriteString call to writeStringChecker") } } func testMultiWriter(t *testing.T, sink interface { - Writer + io.Writer Stringer }, ) { var buf bytes.Buffer - mw := MultiWriter(&buf, sink) + mw := io.MultiWriter(&buf, sink) sourceString := "My input text." source := strings.NewReader(sourceString) - written, err := Copy(mw, source) + written, err := io.Copy(mw, source) if written != int64(len(sourceString)) { t.Errorf("short write of %d, not %d", written, len(sourceString)) @@ -158,7 +159,7 @@ func TestMultiWriterSingleChainFlatten(t *testing.T) { n := runtime.Callers(0, pc) var myDepth = callDepth(pc[:n]) var writeDepth int // will contain the depth from which writerFunc.Writer was called - var w Writer = MultiWriter(writerFunc(func(p []byte) (int, error) { + var w io.Writer = io.MultiWriter(writerFunc(func(p []byte) (int, error) { n := runtime.Callers(1, pc) writeDepth += callDepth(pc[:n]) return 0, nil @@ -167,10 +168,10 @@ func TestMultiWriterSingleChainFlatten(t *testing.T) { mw := w // chain a bunch of multiWriters for i := 0; i < 100; i++ { - mw = MultiWriter(w) + mw = io.MultiWriter(w) } - mw = MultiWriter(w, mw, w, mw) + mw = io.MultiWriter(w, mw, w, mw) mw.Write(nil) // don't care about errors, just want to check the call-depth for Write if writeDepth != 4*(myDepth+2) { // 2 should be multiWriter.Write and writerFunc.Write @@ -182,25 +183,25 @@ func TestMultiWriterSingleChainFlatten(t *testing.T) { func TestMultiWriterError(t *testing.T) { f1 := writerFunc(func(p []byte) (int, error) { - return len(p) / 2, ErrShortWrite + return len(p) / 2, io.ErrShortWrite }) f2 := writerFunc(func(p []byte) (int, error) { t.Errorf("MultiWriter called f2.Write") return len(p), nil }) - w := MultiWriter(f1, f2) + w := io.MultiWriter(f1, f2) n, err := w.Write(make([]byte, 100)) - if n != 50 || err != ErrShortWrite { + if n != 50 || err != io.ErrShortWrite { t.Errorf("Write = %d, %v, want 50, ErrShortWrite", n, err) } } // Test that MultiReader copies the input slice and is insulated from future modification. func TestMultiReaderCopy(t *testing.T) { - slice := []Reader{strings.NewReader("hello world")} - r := MultiReader(slice...) + slice := []io.Reader{strings.NewReader("hello world")} + r := io.MultiReader(slice...) slice[0] = nil - data, err := ReadAll(r) + data, err := io.ReadAll(r) if err != nil || string(data) != "hello world" { t.Errorf("ReadAll() = %q, %v, want %q, nil", data, err, "hello world") } @@ -209,8 +210,8 @@ func TestMultiReaderCopy(t *testing.T) { // Test that MultiWriter copies the input slice and is insulated from future modification. func TestMultiWriterCopy(t *testing.T) { var buf bytes.Buffer - slice := []Writer{&buf} - w := MultiWriter(slice...) + slice := []io.Writer{&buf} + w := io.MultiWriter(slice...) slice[0] = nil n, err := w.Write([]byte("hello world")) if err != nil || n != 11 { @@ -246,7 +247,7 @@ func TestMultiReaderFlatten(t *testing.T) { n := runtime.Callers(0, pc) var myDepth = callDepth(pc[:n]) var readDepth int // will contain the depth from which fakeReader.Read was called - var r Reader = MultiReader(readerFunc(func(p []byte) (int, error) { + var r io.Reader = io.MultiReader(readerFunc(func(p []byte) (int, error) { n := runtime.Callers(1, pc) readDepth = callDepth(pc[:n]) return 0, errors.New("irrelevant") @@ -254,7 +255,7 @@ func TestMultiReaderFlatten(t *testing.T) { // chain a bunch of multiReaders for i := 0; i < 100; i++ { - r = MultiReader(r) + r = io.MultiReader(r) } r.Read(nil) // don't care about errors, just want to check the call-depth for Read @@ -277,12 +278,12 @@ func (b byteAndEOFReader) Read(p []byte) (n int, err error) { panic("unexpected call") } p[0] = byte(b) - return 1, EOF + return 1, io.EOF } // This used to yield bytes forever; issue 16795. func TestMultiReaderSingleByteWithEOF(t *testing.T) { - got, err := ReadAll(LimitReader(MultiReader(byteAndEOFReader('a'), byteAndEOFReader('b')), 10)) + got, err := io.ReadAll(io.LimitReader(io.MultiReader(byteAndEOFReader('a'), byteAndEOFReader('b')), 10)) if err != nil { t.Fatal(err) } @@ -296,17 +297,17 @@ func TestMultiReaderSingleByteWithEOF(t *testing.T) { // chain continues to return EOF on its final read, rather than // yielding a (0, EOF). func TestMultiReaderFinalEOF(t *testing.T) { - r := MultiReader(bytes.NewReader(nil), byteAndEOFReader('a')) + r := io.MultiReader(bytes.NewReader(nil), byteAndEOFReader('a')) buf := make([]byte, 2) n, err := r.Read(buf) - if n != 1 || err != EOF { + if n != 1 || err != io.EOF { t.Errorf("got %v, %v; want 1, EOF", n, err) } } /* func TestMultiReaderFreesExhaustedReaders(t *testing.T) { - var mr Reader + var mr io.Reader closed := make(chan struct{}) // The closure ensures that we don't have a live reference to buf1 // on our stack after MultiReader is inlined (Issue 18819). This @@ -314,14 +315,14 @@ func TestMultiReaderFreesExhaustedReaders(t *testing.T) { func() { buf1 := bytes.NewReader([]byte("foo")) buf2 := bytes.NewReader([]byte("bar")) - mr = MultiReader(buf1, buf2) - runtime.SetFinalizer(buf1, func(*bytes.Reader) { + mr = io.MultiReader(buf1, buf2) + runtime.SetFinalizer(buf1, func(*bytes.io.Reader) { close(closed) }) }() buf := make([]byte, 4) - if n, err := ReadFull(mr, buf); err != nil || string(buf) != "foob" { + if n, err := io.ReadFull(mr, buf); err != nil || string(buf) != "foob" { t.Fatalf(`ReadFull = %d (%q), %v; want 3, "foo", nil`, n, buf[:n], err) } @@ -332,7 +333,7 @@ func TestMultiReaderFreesExhaustedReaders(t *testing.T) { t.Fatal("timeout waiting for collection of buf1") } - if n, err := ReadFull(mr, buf[:2]); err != nil || string(buf[:2]) != "ar" { + if n, err := io.ReadFull(mr, buf[:2]); err != nil || string(buf[:2]) != "ar" { t.Fatalf(`ReadFull = %d (%q), %v; want 2, "ar", nil`, n, buf[:n], err) } } @@ -342,21 +343,21 @@ func TestInterleavedMultiReader(t *testing.T) { r1 := strings.NewReader("123") r2 := strings.NewReader("45678") - mr1 := MultiReader(r1, r2) - mr2 := MultiReader(mr1) + mr1 := io.MultiReader(r1, r2) + mr2 := io.MultiReader(mr1) buf := make([]byte, 4) // Have mr2 use mr1's []Readers. // Consume r1 (and clear it for GC to handle) and consume part of r2. - n, err := ReadFull(mr2, buf) + n, err := io.ReadFull(mr2, buf) if got := string(buf[:n]); got != "1234" || err != nil { t.Errorf(`ReadFull(mr2) = (%q, %v), want ("1234", nil)`, got, err) } // Consume the rest of r2 via mr1. // This should not panic even though mr2 cleared r1. - n, err = ReadFull(mr1, buf) + n, err = io.ReadFull(mr1, buf) if got := string(buf[:n]); got != "5678" || err != nil { t.Errorf(`ReadFull(mr1) = (%q, %v), want ("5678", nil)`, got, err) } diff --git a/gnovm/stdlibs/math/all_test.gno b/gnovm/stdlibs/math/all_test.gno index a138123b5ab..de956248bd0 100644 --- a/gnovm/stdlibs/math/all_test.gno +++ b/gnovm/stdlibs/math/all_test.gno @@ -3347,7 +3347,7 @@ func TestTrigReduce(t *testing.T) { // https://golang.org/issue/201 type floatTest struct { - val interface{} + val any name string str string } diff --git a/gnovm/stdlibs/math/bits/bits_test.gno b/gnovm/stdlibs/math/bits/bits_test.gno index e2834cacf7c..b92de1b94d2 100644 --- a/gnovm/stdlibs/math/bits/bits_test.gno +++ b/gnovm/stdlibs/math/bits/bits_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package bits +package bits_test import ( + "math/bits" "testing" ) @@ -14,7 +15,7 @@ func TestLeadingZeros(t *testing.T) { for k := 0; k < 64-8; k++ { x := uint64(i) << uint(k) if x <= 1<<8-1 { - got := LeadingZeros8(uint8(x)) + got := bits.LeadingZeros8(uint8(x)) want := nlz - k + (8 - 8) if x == 0 { want = 8 @@ -25,7 +26,7 @@ func TestLeadingZeros(t *testing.T) { } if x <= 1<<16-1 { - got := LeadingZeros16(uint16(x)) + got := bits.LeadingZeros16(uint16(x)) want := nlz - k + (16 - 8) if x == 0 { want = 16 @@ -36,7 +37,7 @@ func TestLeadingZeros(t *testing.T) { } if x <= 1<<32-1 { - got := LeadingZeros32(uint32(x)) + got := bits.LeadingZeros32(uint32(x)) want := nlz - k + (32 - 8) if x == 0 { want = 32 @@ -44,8 +45,8 @@ func TestLeadingZeros(t *testing.T) { if got != want { t.Fatalf("LeadingZeros32(%#08x) == %d; want %d", x, got, want) } - if UintSize == 32 { - got = LeadingZeros(uint(x)) + if bits.UintSize == 32 { + got = bits.LeadingZeros(uint(x)) if got != want { t.Fatalf("LeadingZeros(%#08x) == %d; want %d", x, got, want) } @@ -53,7 +54,7 @@ func TestLeadingZeros(t *testing.T) { } if x <= 1<<64-1 { - got := LeadingZeros64(uint64(x)) + got := bits.LeadingZeros64(uint64(x)) want := nlz - k + (64 - 8) if x == 0 { want = 64 @@ -61,8 +62,8 @@ func TestLeadingZeros(t *testing.T) { if got != want { t.Fatalf("LeadingZeros64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got = LeadingZeros(uint(x)) + if bits.UintSize == 64 { + got = bits.LeadingZeros(uint(x)) if got != want { t.Fatalf("LeadingZeros(%#016x) == %d; want %d", x, got, want) } @@ -75,7 +76,7 @@ func TestLeadingZeros(t *testing.T) { // Exported (global) variable serving as input for some // of the benchmarks to ensure side-effect free calls // are not optimized away. -var Input uint64 = DeBruijn64 +var Input uint64 = bits.DeBruijn64 // Exported (global) variable to store function results // during benchmarking to ensure side-effect free calls @@ -85,7 +86,7 @@ var Output int func BenchmarkLeadingZeros(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros(uint(Input) >> (uint(i) % UintSize)) + s += bits.LeadingZeros(uint(Input) >> (uint(i) % bits.UintSize)) } Output = s } @@ -93,7 +94,7 @@ func BenchmarkLeadingZeros(b *testing.B) { func BenchmarkLeadingZeros8(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros8(uint8(Input) >> (uint(i) % 8)) + s += bits.LeadingZeros8(uint8(Input) >> (uint(i) % 8)) } Output = s } @@ -101,7 +102,7 @@ func BenchmarkLeadingZeros8(b *testing.B) { func BenchmarkLeadingZeros16(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros16(uint16(Input) >> (uint(i) % 16)) + s += bits.LeadingZeros16(uint16(Input) >> (uint(i) % 16)) } Output = s } @@ -109,7 +110,7 @@ func BenchmarkLeadingZeros16(b *testing.B) { func BenchmarkLeadingZeros32(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros32(uint32(Input) >> (uint(i) % 32)) + s += bits.LeadingZeros32(uint32(Input) >> (uint(i) % 32)) } Output = s } @@ -117,7 +118,7 @@ func BenchmarkLeadingZeros32(b *testing.B) { func BenchmarkLeadingZeros64(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += LeadingZeros64(uint64(Input) >> (uint(i) % 64)) + s += bits.LeadingZeros64(uint64(Input) >> (uint(i) % 64)) } Output = s } @@ -129,7 +130,7 @@ func TestTrailingZeros(t *testing.T) { x := uint64(i) << uint(k) want := ntz + k if x <= 1<<8-1 { - got := TrailingZeros8(uint8(x)) + got := bits.TrailingZeros8(uint8(x)) if x == 0 { want = 8 } @@ -139,7 +140,7 @@ func TestTrailingZeros(t *testing.T) { } if x <= 1<<16-1 { - got := TrailingZeros16(uint16(x)) + got := bits.TrailingZeros16(uint16(x)) if x == 0 { want = 16 } @@ -149,15 +150,15 @@ func TestTrailingZeros(t *testing.T) { } if x <= 1<<32-1 { - got := TrailingZeros32(uint32(x)) + got := bits.TrailingZeros32(uint32(x)) if x == 0 { want = 32 } if got != want { t.Fatalf("TrailingZeros32(%#08x) == %d; want %d", x, got, want) } - if UintSize == 32 { - got = TrailingZeros(uint(x)) + if bits.UintSize == 32 { + got = bits.TrailingZeros(uint(x)) if got != want { t.Fatalf("TrailingZeros(%#08x) == %d; want %d", x, got, want) } @@ -165,15 +166,15 @@ func TestTrailingZeros(t *testing.T) { } if x <= 1<<64-1 { - got := TrailingZeros64(uint64(x)) + got := bits.TrailingZeros64(uint64(x)) if x == 0 { want = 64 } if got != want { t.Fatalf("TrailingZeros64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got = TrailingZeros(uint(x)) + if bits.UintSize == 64 { + got = bits.TrailingZeros(uint(x)) if got != want { t.Fatalf("TrailingZeros(%#016x) == %d; want %d", x, got, want) } @@ -186,7 +187,7 @@ func TestTrailingZeros(t *testing.T) { func BenchmarkTrailingZeros(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros(uint(Input) << (uint(i) % UintSize)) + s += bits.TrailingZeros(uint(Input) << (uint(i) % bits.UintSize)) } Output = s } @@ -194,7 +195,7 @@ func BenchmarkTrailingZeros(b *testing.B) { func BenchmarkTrailingZeros8(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros8(uint8(Input) << (uint(i) % 8)) + s += bits.TrailingZeros8(uint8(Input) << (uint(i) % 8)) } Output = s } @@ -202,7 +203,7 @@ func BenchmarkTrailingZeros8(b *testing.B) { func BenchmarkTrailingZeros16(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros16(uint16(Input) << (uint(i) % 16)) + s += bits.TrailingZeros16(uint16(Input) << (uint(i) % 16)) } Output = s } @@ -210,7 +211,7 @@ func BenchmarkTrailingZeros16(b *testing.B) { func BenchmarkTrailingZeros32(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros32(uint32(Input) << (uint(i) % 32)) + s += bits.TrailingZeros32(uint32(Input) << (uint(i) % 32)) } Output = s } @@ -218,7 +219,7 @@ func BenchmarkTrailingZeros32(b *testing.B) { func BenchmarkTrailingZeros64(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += TrailingZeros64(uint64(Input) << (uint(i) % 64)) + s += bits.TrailingZeros64(uint64(Input) << (uint(i) % 64)) } Output = s } @@ -244,26 +245,26 @@ func TestOnesCount(t *testing.T) { func testOnesCount(t *testing.T, x uint64, want int) { if x <= 1<<8-1 { - got := OnesCount8(uint8(x)) + got := bits.OnesCount8(uint8(x)) if got != want { t.Fatalf("OnesCount8(%#02x) == %d; want %d", uint8(x), got, want) } } if x <= 1<<16-1 { - got := OnesCount16(uint16(x)) + got := bits.OnesCount16(uint16(x)) if got != want { t.Fatalf("OnesCount16(%#04x) == %d; want %d", uint16(x), got, want) } } if x <= 1<<32-1 { - got := OnesCount32(uint32(x)) + got := bits.OnesCount32(uint32(x)) if got != want { t.Fatalf("OnesCount32(%#08x) == %d; want %d", uint32(x), got, want) } - if UintSize == 32 { - got = OnesCount(uint(x)) + if bits.UintSize == 32 { + got = bits.OnesCount(uint(x)) if got != want { t.Fatalf("OnesCount(%#08x) == %d; want %d", uint32(x), got, want) } @@ -271,12 +272,12 @@ func testOnesCount(t *testing.T, x uint64, want int) { } if x <= 1<<64-1 { - got := OnesCount64(uint64(x)) + got := bits.OnesCount64(uint64(x)) if got != want { t.Fatalf("OnesCount64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got = OnesCount(uint(x)) + if bits.UintSize == 64 { + got = bits.OnesCount(uint(x)) if got != want { t.Fatalf("OnesCount(%#016x) == %d; want %d", x, got, want) } @@ -287,7 +288,7 @@ func testOnesCount(t *testing.T, x uint64, want int) { func BenchmarkOnesCount(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount(uint(Input)) + s += bits.OnesCount(uint(Input)) } Output = s } @@ -295,7 +296,7 @@ func BenchmarkOnesCount(b *testing.B) { func BenchmarkOnesCount8(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount8(uint8(Input)) + s += bits.OnesCount8(uint8(Input)) } Output = s } @@ -303,7 +304,7 @@ func BenchmarkOnesCount8(b *testing.B) { func BenchmarkOnesCount16(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount16(uint16(Input)) + s += bits.OnesCount16(uint16(Input)) } Output = s } @@ -311,7 +312,7 @@ func BenchmarkOnesCount16(b *testing.B) { func BenchmarkOnesCount32(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount32(uint32(Input)) + s += bits.OnesCount32(uint32(Input)) } Output = s } @@ -319,78 +320,78 @@ func BenchmarkOnesCount32(b *testing.B) { func BenchmarkOnesCount64(b *testing.B) { var s int for i := 0; i < b.N; i++ { - s += OnesCount64(uint64(Input)) + s += bits.OnesCount64(uint64(Input)) } Output = s } func TestRotateLeft(t *testing.T) { - var m uint64 = DeBruijn64 + var m uint64 = bits.DeBruijn64 for k := uint(0); k < 128; k++ { x8 := uint8(m) - got8 := RotateLeft8(x8, int(k)) + got8 := bits.RotateLeft8(x8, int(k)) want8 := x8<<(k&0x7) | x8>>(8-k&0x7) if got8 != want8 { t.Fatalf("RotateLeft8(%#02x, %d) == %#02x; want %#02x", x8, k, got8, want8) } - got8 = RotateLeft8(want8, -int(k)) + got8 = bits.RotateLeft8(want8, -int(k)) if got8 != x8 { t.Fatalf("RotateLeft8(%#02x, -%d) == %#02x; want %#02x", want8, k, got8, x8) } x16 := uint16(m) - got16 := RotateLeft16(x16, int(k)) + got16 := bits.RotateLeft16(x16, int(k)) want16 := x16<<(k&0xf) | x16>>(16-k&0xf) if got16 != want16 { t.Fatalf("RotateLeft16(%#04x, %d) == %#04x; want %#04x", x16, k, got16, want16) } - got16 = RotateLeft16(want16, -int(k)) + got16 = bits.RotateLeft16(want16, -int(k)) if got16 != x16 { t.Fatalf("RotateLeft16(%#04x, -%d) == %#04x; want %#04x", want16, k, got16, x16) } x32 := uint32(m) - got32 := RotateLeft32(x32, int(k)) + got32 := bits.RotateLeft32(x32, int(k)) want32 := x32<<(k&0x1f) | x32>>(32-k&0x1f) if got32 != want32 { t.Fatalf("RotateLeft32(%#08x, %d) == %#08x; want %#08x", x32, k, got32, want32) } - got32 = RotateLeft32(want32, -int(k)) + got32 = bits.RotateLeft32(want32, -int(k)) if got32 != x32 { t.Fatalf("RotateLeft32(%#08x, -%d) == %#08x; want %#08x", want32, k, got32, x32) } - if UintSize == 32 { + if bits.UintSize == 32 { x := uint(m) - got := RotateLeft(x, int(k)) + got := bits.RotateLeft(x, int(k)) want := x<<(k&0x1f) | x>>(32-k&0x1f) if got != want { t.Fatalf("RotateLeft(%#08x, %d) == %#08x; want %#08x", x, k, got, want) } - got = RotateLeft(want, -int(k)) + got = bits.RotateLeft(want, -int(k)) if got != x { t.Fatalf("RotateLeft(%#08x, -%d) == %#08x; want %#08x", want, k, got, x) } } x64 := uint64(m) - got64 := RotateLeft64(x64, int(k)) + got64 := bits.RotateLeft64(x64, int(k)) want64 := x64<<(k&0x3f) | x64>>(64-k&0x3f) if got64 != want64 { t.Fatalf("RotateLeft64(%#016x, %d) == %#016x; want %#016x", x64, k, got64, want64) } - got64 = RotateLeft64(want64, -int(k)) + got64 = bits.RotateLeft64(want64, -int(k)) if got64 != x64 { t.Fatalf("RotateLeft64(%#016x, -%d) == %#016x; want %#016x", want64, k, got64, x64) } - if UintSize == 64 { + if bits.UintSize == 64 { x := uint(m) - got := RotateLeft(x, int(k)) + got := bits.RotateLeft(x, int(k)) want := x<<(k&0x3f) | x>>(64-k&0x3f) if got != want { t.Fatalf("RotateLeft(%#016x, %d) == %#016x; want %#016x", x, k, got, want) } - got = RotateLeft(want, -int(k)) + got = bits.RotateLeft(want, -int(k)) if got != x { t.Fatalf("RotateLeft(%#08x, -%d) == %#08x; want %#08x", want, k, got, x) } @@ -401,7 +402,7 @@ func TestRotateLeft(t *testing.T) { func BenchmarkRotateLeft(b *testing.B) { var s uint for i := 0; i < b.N; i++ { - s += RotateLeft(uint(Input), i) + s += bits.RotateLeft(uint(Input), i) } Output = int(s) } @@ -409,7 +410,7 @@ func BenchmarkRotateLeft(b *testing.B) { func BenchmarkRotateLeft8(b *testing.B) { var s uint8 for i := 0; i < b.N; i++ { - s += RotateLeft8(uint8(Input), i) + s += bits.RotateLeft8(uint8(Input), i) } Output = int(s) } @@ -417,7 +418,7 @@ func BenchmarkRotateLeft8(b *testing.B) { func BenchmarkRotateLeft16(b *testing.B) { var s uint16 for i := 0; i < b.N; i++ { - s += RotateLeft16(uint16(Input), i) + s += bits.RotateLeft16(uint16(Input), i) } Output = int(s) } @@ -425,7 +426,7 @@ func BenchmarkRotateLeft16(b *testing.B) { func BenchmarkRotateLeft32(b *testing.B) { var s uint32 for i := 0; i < b.N; i++ { - s += RotateLeft32(uint32(Input), i) + s += bits.RotateLeft32(uint32(Input), i) } Output = int(s) } @@ -433,7 +434,7 @@ func BenchmarkRotateLeft32(b *testing.B) { func BenchmarkRotateLeft64(b *testing.B) { var s uint64 for i := 0; i < b.N; i++ { - s += RotateLeft64(uint64(Input), i) + s += bits.RotateLeft64(uint64(Input), i) } Output = int(s) } @@ -474,41 +475,41 @@ func TestReverse(t *testing.T) { func testReverse(t *testing.T, x64, want64 uint64) { x8 := uint8(x64) - got8 := Reverse8(x8) + got8 := bits.Reverse8(x8) want8 := uint8(want64 >> (64 - 8)) if got8 != want8 { t.Fatalf("Reverse8(%#02x) == %#02x; want %#02x", x8, got8, want8) } x16 := uint16(x64) - got16 := Reverse16(x16) + got16 := bits.Reverse16(x16) want16 := uint16(want64 >> (64 - 16)) if got16 != want16 { t.Fatalf("Reverse16(%#04x) == %#04x; want %#04x", x16, got16, want16) } x32 := uint32(x64) - got32 := Reverse32(x32) + got32 := bits.Reverse32(x32) want32 := uint32(want64 >> (64 - 32)) if got32 != want32 { t.Fatalf("Reverse32(%#08x) == %#08x; want %#08x", x32, got32, want32) } - if UintSize == 32 { + if bits.UintSize == 32 { x := uint(x32) - got := Reverse(x) + got := bits.Reverse(x) want := uint(want32) if got != want { t.Fatalf("Reverse(%#08x) == %#08x; want %#08x", x, got, want) } } - got64 := Reverse64(x64) + got64 := bits.Reverse64(x64) if got64 != want64 { t.Fatalf("Reverse64(%#016x) == %#016x; want %#016x", x64, got64, want64) } - if UintSize == 64 { + if bits.UintSize == 64 { x := uint(x64) - got := Reverse(x) + got := bits.Reverse(x) want := uint(want64) if got != want { t.Fatalf("Reverse(%#08x) == %#016x; want %#016x", x, got, want) @@ -519,7 +520,7 @@ func testReverse(t *testing.T, x64, want64 uint64) { func BenchmarkReverse(b *testing.B) { var s uint for i := 0; i < b.N; i++ { - s += Reverse(uint(i)) + s += bits.Reverse(uint(i)) } Output = int(s) } @@ -527,7 +528,7 @@ func BenchmarkReverse(b *testing.B) { func BenchmarkReverse8(b *testing.B) { var s uint8 for i := 0; i < b.N; i++ { - s += Reverse8(uint8(i)) + s += bits.Reverse8(uint8(i)) } Output = int(s) } @@ -535,7 +536,7 @@ func BenchmarkReverse8(b *testing.B) { func BenchmarkReverse16(b *testing.B) { var s uint16 for i := 0; i < b.N; i++ { - s += Reverse16(uint16(i)) + s += bits.Reverse16(uint16(i)) } Output = int(s) } @@ -543,7 +544,7 @@ func BenchmarkReverse16(b *testing.B) { func BenchmarkReverse32(b *testing.B) { var s uint32 for i := 0; i < b.N; i++ { - s += Reverse32(uint32(i)) + s += bits.Reverse32(uint32(i)) } Output = int(s) } @@ -551,7 +552,7 @@ func BenchmarkReverse32(b *testing.B) { func BenchmarkReverse64(b *testing.B) { var s uint64 for i := 0; i < b.N; i++ { - s += Reverse64(uint64(i)) + s += bits.Reverse64(uint64(i)) } Output = int(s) } @@ -577,34 +578,34 @@ func TestReverseBytes(t *testing.T) { func testReverseBytes(t *testing.T, x64, want64 uint64) { x16 := uint16(x64) - got16 := ReverseBytes16(x16) + got16 := bits.ReverseBytes16(x16) want16 := uint16(want64 >> (64 - 16)) if got16 != want16 { t.Fatalf("ReverseBytes16(%#04x) == %#04x; want %#04x", x16, got16, want16) } x32 := uint32(x64) - got32 := ReverseBytes32(x32) + got32 := bits.ReverseBytes32(x32) want32 := uint32(want64 >> (64 - 32)) if got32 != want32 { t.Fatalf("ReverseBytes32(%#08x) == %#08x; want %#08x", x32, got32, want32) } - if UintSize == 32 { + if bits.UintSize == 32 { x := uint(x32) - got := ReverseBytes(x) + got := bits.ReverseBytes(x) want := uint(want32) if got != want { t.Fatalf("ReverseBytes(%#08x) == %#08x; want %#08x", x, got, want) } } - got64 := ReverseBytes64(x64) + got64 := bits.ReverseBytes64(x64) if got64 != want64 { t.Fatalf("ReverseBytes64(%#016x) == %#016x; want %#016x", x64, got64, want64) } - if UintSize == 64 { + if bits.UintSize == 64 { x := uint(x64) - got := ReverseBytes(x) + got := bits.ReverseBytes(x) want := uint(want64) if got != want { t.Fatalf("ReverseBytes(%#016x) == %#016x; want %#016x", x, got, want) @@ -615,7 +616,7 @@ func testReverseBytes(t *testing.T, x64, want64 uint64) { func BenchmarkReverseBytes(b *testing.B) { var s uint for i := 0; i < b.N; i++ { - s += ReverseBytes(uint(i)) + s += bits.ReverseBytes(uint(i)) } Output = int(s) } @@ -623,7 +624,7 @@ func BenchmarkReverseBytes(b *testing.B) { func BenchmarkReverseBytes16(b *testing.B) { var s uint16 for i := 0; i < b.N; i++ { - s += ReverseBytes16(uint16(i)) + s += bits.ReverseBytes16(uint16(i)) } Output = int(s) } @@ -631,7 +632,7 @@ func BenchmarkReverseBytes16(b *testing.B) { func BenchmarkReverseBytes32(b *testing.B) { var s uint32 for i := 0; i < b.N; i++ { - s += ReverseBytes32(uint32(i)) + s += bits.ReverseBytes32(uint32(i)) } Output = int(s) } @@ -639,7 +640,7 @@ func BenchmarkReverseBytes32(b *testing.B) { func BenchmarkReverseBytes64(b *testing.B) { var s uint64 for i := 0; i < b.N; i++ { - s += ReverseBytes64(uint64(i)) + s += bits.ReverseBytes64(uint64(i)) } Output = int(s) } @@ -654,26 +655,26 @@ func TestLen(t *testing.T) { want = length + k } if x <= 1<<8-1 { - got := Len8(uint8(x)) + got := bits.Len8(uint8(x)) if got != want { t.Fatalf("Len8(%#02x) == %d; want %d", x, got, want) } } if x <= 1<<16-1 { - got := Len16(uint16(x)) + got := bits.Len16(uint16(x)) if got != want { t.Fatalf("Len16(%#04x) == %d; want %d", x, got, want) } } if x <= 1<<32-1 { - got := Len32(uint32(x)) + got := bits.Len32(uint32(x)) if got != want { t.Fatalf("Len32(%#08x) == %d; want %d", x, got, want) } - if UintSize == 32 { - got := Len(uint(x)) + if bits.UintSize == 32 { + got := bits.Len(uint(x)) if got != want { t.Fatalf("Len(%#08x) == %d; want %d", x, got, want) } @@ -681,12 +682,12 @@ func TestLen(t *testing.T) { } if x <= 1<<64-1 { - got := Len64(uint64(x)) + got := bits.Len64(uint64(x)) if got != want { t.Fatalf("Len64(%#016x) == %d; want %d", x, got, want) } - if UintSize == 64 { - got := Len(uint(x)) + if bits.UintSize == 64 { + got := bits.Len(uint(x)) if got != want { t.Fatalf("Len(%#016x) == %d; want %d", x, got, want) } @@ -697,7 +698,7 @@ func TestLen(t *testing.T) { } const ( - _M = 1< 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c != 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c == 1 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c != 1 { return x } panic("overflow") }, func(a, b uint64) uint64 { - x, c := Add64(a, b, 0) + x, c := bits.Add64(a, b, 0) if c == 0 { return x } @@ -863,35 +864,35 @@ func TestSub64OverflowPanic(t *testing.T) { // These are designed to improve coverage of compiler intrinsics. tests := []func(uint64, uint64) uint64{ func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c > 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c != 0 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c == 1 { panic("overflow") } return x }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c != 1 { return x } panic("overflow") }, func(a, b uint64) uint64 { - x, c := Sub64(a, b, 0) + x, c := bits.Sub64(a, b, 0) if c == 0 { return x } @@ -937,19 +938,19 @@ func TestMulDiv(t *testing.T) { x, y uint hi, lo, r uint }{ - {1 << (UintSize - 1), 2, 1, 0, 1}, + {1 << (bits.UintSize - 1), 2, 1, 0, 1}, {_M, _M, _M - 1, 1, 42}, } { - testMul("Mul", Mul, a.x, a.y, a.hi, a.lo) - testMul("Mul symmetric", Mul, a.y, a.x, a.hi, a.lo) - testDiv("Div", Div, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div symmetric", Div, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul", bits.Mul, a.x, a.y, a.hi, a.lo) + testMul("Mul symmetric", bits.Mul, a.y, a.x, a.hi, a.lo) + testDiv("Div", bits.Div, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div symmetric", bits.Div, a.hi, a.lo+a.r, a.x, a.y, a.r) // The above code can't test intrinsic implementation, because the passed function is not called directly. // The following code uses a closure to test the intrinsic version in case the function is intrinsified. - testMul("Mul intrinsic", func(x, y uint) (uint, uint) { return Mul(x, y) }, a.x, a.y, a.hi, a.lo) - testMul("Mul intrinsic symmetric", func(x, y uint) (uint, uint) { return Mul(x, y) }, a.y, a.x, a.hi, a.lo) - testDiv("Div intrinsic", func(hi, lo, y uint) (uint, uint) { return Div(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div intrinsic symmetric", func(hi, lo, y uint) (uint, uint) { return Div(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul intrinsic", func(x, y uint) (uint, uint) { return bits.Mul(x, y) }, a.x, a.y, a.hi, a.lo) + testMul("Mul intrinsic symmetric", func(x, y uint) (uint, uint) { return bits.Mul(x, y) }, a.y, a.x, a.hi, a.lo) + testDiv("Div intrinsic", func(hi, lo, y uint) (uint, uint) { return bits.Div(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div intrinsic symmetric", func(hi, lo, y uint) (uint, uint) { return bits.Div(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) } } @@ -974,10 +975,10 @@ func TestMulDiv32(t *testing.T) { {0xc47dfa8c, 50911, 0x98a4, 0x998587f4, 13}, {_M32, _M32, _M32 - 1, 1, 42}, } { - testMul("Mul32", Mul32, a.x, a.y, a.hi, a.lo) - testMul("Mul32 symmetric", Mul32, a.y, a.x, a.hi, a.lo) - testDiv("Div32", Div32, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div32 symmetric", Div32, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul32", bits.Mul32, a.x, a.y, a.hi, a.lo) + testMul("Mul32 symmetric", bits.Mul32, a.y, a.x, a.hi, a.lo) + testDiv("Div32", bits.Div32, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div32 symmetric", bits.Div32, a.hi, a.lo+a.r, a.x, a.y, a.r) } } @@ -1002,16 +1003,16 @@ func TestMulDiv64(t *testing.T) { {0x3626229738a3b9, 0xd8988a9f1cc4a61, 0x2dd0712657fe8, 0x9dd6a3364c358319, 13}, {_M64, _M64, _M64 - 1, 1, 42}, } { - testMul("Mul64", Mul64, a.x, a.y, a.hi, a.lo) - testMul("Mul64 symmetric", Mul64, a.y, a.x, a.hi, a.lo) - testDiv("Div64", Div64, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div64 symmetric", Div64, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul64", bits.Mul64, a.x, a.y, a.hi, a.lo) + testMul("Mul64 symmetric", bits.Mul64, a.y, a.x, a.hi, a.lo) + testDiv("Div64", bits.Div64, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div64 symmetric", bits.Div64, a.hi, a.lo+a.r, a.x, a.y, a.r) // The above code can't test intrinsic implementation, because the passed function is not called directly. // The following code uses a closure to test the intrinsic version in case the function is intrinsified. - testMul("Mul64 intrinsic", func(x, y uint64) (uint64, uint64) { return Mul64(x, y) }, a.x, a.y, a.hi, a.lo) - testMul("Mul64 intrinsic symmetric", func(x, y uint64) (uint64, uint64) { return Mul64(x, y) }, a.y, a.x, a.hi, a.lo) - testDiv("Div64 intrinsic", func(hi, lo, y uint64) (uint64, uint64) { return Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) - testDiv("Div64 intrinsic symmetric", func(hi, lo, y uint64) (uint64, uint64) { return Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) + testMul("Mul64 intrinsic", func(x, y uint64) (uint64, uint64) { return bits.Mul64(x, y) }, a.x, a.y, a.hi, a.lo) + testMul("Mul64 intrinsic symmetric", func(x, y uint64) (uint64, uint64) { return bits.Mul64(x, y) }, a.y, a.x, a.hi, a.lo) + testDiv("Div64 intrinsic", func(hi, lo, y uint64) (uint64, uint64) { return bits.Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.y, a.x, a.r) + testDiv("Div64 intrinsic symmetric", func(hi, lo, y uint64) (uint64, uint64) { return bits.Div64(hi, lo, y) }, a.hi, a.lo+a.r, a.x, a.y, a.r) } } @@ -1020,11 +1021,11 @@ func TestDivPanicOverflow(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div should have panicked when y<=hi") - } else if err != overflowError { - t.Errorf("Div expected panic: %q, got: %v ", overflowError, err) + } else if err != bits.OverflowError { + t.Errorf("Div expected panic: %q, got: %v ", bits.OverflowError, err) } }() - q, r := Div(1, 0, 1) + q, r := bits.Div(1, 0, 1) t.Errorf("undefined q, r = %v, %v calculated when Div should have panicked", q, r) } @@ -1033,11 +1034,11 @@ func TestDiv32PanicOverflow(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div32 should have panicked when y<=hi") - } else if err != overflowError { - t.Errorf("Div32 expected panic: %q, got: %v ", overflowError, err) + } else if err != bits.OverflowError { + t.Errorf("Div32 expected panic: %q, got: %v ", bits.OverflowError, err) } }() - q, r := Div32(1, 0, 1) + q, r := bits.Div32(1, 0, 1) t.Errorf("undefined q, r = %v, %v calculated when Div32 should have panicked", q, r) } @@ -1046,11 +1047,11 @@ func TestDiv64PanicOverflow(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div64 should have panicked when y<=hi") - } else if err != overflowError { - t.Errorf("Div64 expected panic: %q, got: %v ", overflowError, err) + } else if err != bits.OverflowError { + t.Errorf("Div64 expected panic: %q, got: %v ", bits.OverflowError, err) } }() - q, r := Div64(1, 0, 1) + q, r := bits.Div64(1, 0, 1) t.Errorf("undefined q, r = %v, %v calculated when Div64 should have panicked", q, r) } @@ -1059,11 +1060,11 @@ func TestDivPanicZero(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div should have panicked when y==0") - } else if err != divideError { - t.Errorf("Div expected panic: %q, got: %q ", divideError, err) + } else if err != bits.DivideError { + t.Errorf("Div expected panic: %q, got: %q ", bits.DivideError, err) } }() - q, r := Div(1, 1, 0) + q, r := bits.Div(1, 1, 0) t.Errorf("undefined q, r = %v, %v calculated when Div should have panicked", q, r) } @@ -1072,11 +1073,11 @@ func TestDiv32PanicZero(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div32 should have panicked when y==0") - } else if err != divideError { - t.Errorf("Div32 expected panic: %q, got: %q ", divideError, err) + } else if err != bits.DivideError { + t.Errorf("Div32 expected panic: %q, got: %q ", bits.DivideError, err) } }() - q, r := Div32(1, 1, 0) + q, r := bits.Div32(1, 1, 0) t.Errorf("undefined q, r = %v, %v calculated when Div32 should have panicked", q, r) } @@ -1085,11 +1086,11 @@ func TestDiv64PanicZero(t *testing.T) { defer func() { if err := recover(); err == nil { t.Error("Div64 should have panicked when y==0") - } else if err != divideError { - t.Errorf("Div64 expected panic: %q, got: %q ", divideError, err) + } else if err != bits.DivideError { + t.Errorf("Div64 expected panic: %q, got: %q ", bits.DivideError, err) } }() - q, r := Div64(1, 1, 0) + q, r := bits.Div64(1, 1, 0) t.Errorf("undefined q, r = %v, %v calculated when Div64 should have panicked", q, r) } @@ -1098,8 +1099,8 @@ func TestRem32(t *testing.T) { // 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++ { - r := Rem32(hi, lo, y) - _, r2 := Div32(hi, lo, y) + r := bits.Rem32(hi, lo, y) + _, r2 := bits.Div32(hi, lo, y) if r != r2 { t.Errorf("Rem32(%v, %v, %v) returned %v, but Div32 returned rem %v", hi, lo, y, r, r2) } @@ -1111,8 +1112,8 @@ func TestRem32Overflow(t *testing.T) { // To trigger a quotient overflow, we need y <= hi hi, lo, y := uint32(510510), uint32(9699690), uint32(7) for i := 0; i < 1000; i++ { - r := Rem32(hi, lo, y) - _, r2 := Div64(0, uint64(hi)<<32|uint64(lo), uint64(y)) + r := bits.Rem32(hi, lo, y) + _, r2 := bits.Div64(0, uint64(hi)<<32|uint64(lo), uint64(y)) if r != uint32(r2) { t.Errorf("Rem32(%v, %v, %v) returned %v, but Div64 returned rem %v", hi, lo, y, r, r2) } @@ -1125,8 +1126,8 @@ func TestRem64(t *testing.T) { // 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++ { - r := Rem64(hi, lo, y) - _, r2 := Div64(hi, lo, y) + r := bits.Rem64(hi, lo, y) + _, r2 := bits.Div64(hi, lo, y) if r != r2 { t.Errorf("Rem64(%v, %v, %v) returned %v, but Div64 returned rem %v", hi, lo, y, r, r2) } @@ -1155,7 +1156,7 @@ func TestRem64Overflow(t *testing.T) { if rt.hi < rt.y { t.Fatalf("Rem64(%v, %v, %v) is not a test with quo overflow", rt.hi, rt.lo, rt.y) } - rem := Rem64(rt.hi, rt.lo, rt.y) + rem := bits.Rem64(rt.hi, rt.lo, rt.y) if rem != rt.rem { t.Errorf("Rem64(%v, %v, %v) returned %v, wanted %v", rt.hi, rt.lo, rt.y, rem, rt.rem) @@ -1166,7 +1167,7 @@ func TestRem64Overflow(t *testing.T) { func BenchmarkAdd(b *testing.B) { var z, c uint for i := 0; i < b.N; i++ { - z, c = Add(uint(Input), uint(i), c) + z, c = bits.Add(uint(Input), uint(i), c) } Output = int(z + c) } @@ -1174,7 +1175,7 @@ func BenchmarkAdd(b *testing.B) { func BenchmarkAdd32(b *testing.B) { var z, c uint32 for i := 0; i < b.N; i++ { - z, c = Add32(uint32(Input), uint32(i), c) + z, c = bits.Add32(uint32(Input), uint32(i), c) } Output = int(z + c) } @@ -1182,7 +1183,7 @@ func BenchmarkAdd32(b *testing.B) { func BenchmarkAdd64(b *testing.B) { var z, c uint64 for i := 0; i < b.N; i++ { - z, c = Add64(uint64(Input), uint64(i), c) + z, c = bits.Add64(uint64(Input), uint64(i), c) } Output = int(z + c) } @@ -1194,10 +1195,10 @@ func BenchmarkAdd64multiple(b *testing.B) { z3 := uint64(Input) for i := 0; i < b.N; i++ { var c uint64 - z0, c = Add64(z0, uint64(i), c) - z1, c = Add64(z1, uint64(i), c) - z2, c = Add64(z2, uint64(i), c) - z3, _ = Add64(z3, uint64(i), c) + z0, c = bits.Add64(z0, uint64(i), c) + z1, c = bits.Add64(z1, uint64(i), c) + z2, c = bits.Add64(z2, uint64(i), c) + z3, _ = bits.Add64(z3, uint64(i), c) } Output = int(z0 + z1 + z2 + z3) } @@ -1205,7 +1206,7 @@ func BenchmarkAdd64multiple(b *testing.B) { func BenchmarkSub(b *testing.B) { var z, c uint for i := 0; i < b.N; i++ { - z, c = Sub(uint(Input), uint(i), c) + z, c = bits.Sub(uint(Input), uint(i), c) } Output = int(z + c) } @@ -1213,7 +1214,7 @@ func BenchmarkSub(b *testing.B) { func BenchmarkSub32(b *testing.B) { var z, c uint32 for i := 0; i < b.N; i++ { - z, c = Sub32(uint32(Input), uint32(i), c) + z, c = bits.Sub32(uint32(Input), uint32(i), c) } Output = int(z + c) } @@ -1221,7 +1222,7 @@ func BenchmarkSub32(b *testing.B) { func BenchmarkSub64(b *testing.B) { var z, c uint64 for i := 0; i < b.N; i++ { - z, c = Sub64(uint64(Input), uint64(i), c) + z, c = bits.Sub64(uint64(Input), uint64(i), c) } Output = int(z + c) } @@ -1233,10 +1234,10 @@ func BenchmarkSub64multiple(b *testing.B) { z3 := uint64(Input) for i := 0; i < b.N; i++ { var c uint64 - z0, c = Sub64(z0, uint64(i), c) - z1, c = Sub64(z1, uint64(i), c) - z2, c = Sub64(z2, uint64(i), c) - z3, _ = Sub64(z3, uint64(i), c) + z0, c = bits.Sub64(z0, uint64(i), c) + z1, c = bits.Sub64(z1, uint64(i), c) + z2, c = bits.Sub64(z2, uint64(i), c) + z3, _ = bits.Sub64(z3, uint64(i), c) } Output = int(z0 + z1 + z2 + z3) } @@ -1244,7 +1245,7 @@ func BenchmarkSub64multiple(b *testing.B) { func BenchmarkMul(b *testing.B) { var hi, lo uint for i := 0; i < b.N; i++ { - hi, lo = Mul(uint(Input), uint(i)) + hi, lo = bits.Mul(uint(Input), uint(i)) } Output = int(hi + lo) } @@ -1252,7 +1253,7 @@ func BenchmarkMul(b *testing.B) { func BenchmarkMul32(b *testing.B) { var hi, lo uint32 for i := 0; i < b.N; i++ { - hi, lo = Mul32(uint32(Input), uint32(i)) + hi, lo = bits.Mul32(uint32(Input), uint32(i)) } Output = int(hi + lo) } @@ -1260,7 +1261,7 @@ func BenchmarkMul32(b *testing.B) { func BenchmarkMul64(b *testing.B) { var hi, lo uint64 for i := 0; i < b.N; i++ { - hi, lo = Mul64(uint64(Input), uint64(i)) + hi, lo = bits.Mul64(uint64(Input), uint64(i)) } Output = int(hi + lo) } @@ -1268,7 +1269,7 @@ func BenchmarkMul64(b *testing.B) { func BenchmarkDiv(b *testing.B) { var q, r uint for i := 0; i < b.N; i++ { - q, r = Div(1, uint(i), uint(Input)) + q, r = bits.Div(1, uint(i), uint(Input)) } Output = int(q + r) } @@ -1276,7 +1277,7 @@ func BenchmarkDiv(b *testing.B) { func BenchmarkDiv32(b *testing.B) { var q, r uint32 for i := 0; i < b.N; i++ { - q, r = Div32(1, uint32(i), uint32(Input)) + q, r = bits.Div32(1, uint32(i), uint32(Input)) } Output = int(q + r) } @@ -1284,7 +1285,7 @@ func BenchmarkDiv32(b *testing.B) { func BenchmarkDiv64(b *testing.B) { var q, r uint64 for i := 0; i < b.N; i++ { - q, r = Div64(1, uint64(i), uint64(Input)) + q, r = bits.Div64(1, uint64(i), uint64(Input)) } Output = int(q + r) } diff --git a/gnovm/stdlibs/math/bits/export_test.gno b/gnovm/stdlibs/math/bits/export_test.gno index 8c6f9332cca..a0165163d8f 100644 --- a/gnovm/stdlibs/math/bits/export_test.gno +++ b/gnovm/stdlibs/math/bits/export_test.gno @@ -4,4 +4,9 @@ package bits +// exported for tests + const DeBruijn64 = deBruijn64 + +var OverflowError = overflowError +var DivideError = divideError diff --git a/gnovm/stdlibs/math/const.gno b/gnovm/stdlibs/math/const.gno index ad13d53477e..1835c9202ee 100644 --- a/gnovm/stdlibs/math/const.gno +++ b/gnovm/stdlibs/math/const.gno @@ -37,7 +37,7 @@ const ( // Integer limit values. const ( - intSize = 32 << (^uint(0) >> 63) // 32 or 64 + intSize = 64 // Integers are 64 bits only. MaxInt = 1<<(intSize-1) - 1 // == MaxInt64 MinInt = -1 << (intSize - 1) // == MinInt64 diff --git a/gnovm/stdlibs/math/rand/example_test.gno b/gnovm/stdlibs/math/rand/example_test.gno index 83da46b2a3c..3d1119d26d1 100644 --- a/gnovm/stdlibs/math/rand/example_test.gno +++ b/gnovm/stdlibs/math/rand/example_test.gno @@ -48,7 +48,7 @@ func Example_rand() { r := rand.New(rand.NewPCG(1, 2)) // XXX: Go uses tabwriter; we don't have it, so use stdout directly. - show := func(name string, v1, v2, v3 interface{}) { + show := func(name string, v1, v2, v3 any) { fmt.Printf("%s\t%v\t%v\t%v\n", name, v1, v2, v3) } diff --git a/gnovm/stdlibs/math/rand/regress_test.gno b/gnovm/stdlibs/math/rand/regress_test.gno index 6ef6e1e0bf8..900a088ac35 100644 --- a/gnovm/stdlibs/math/rand/regress_test.gno +++ b/gnovm/stdlibs/math/rand/regress_test.gno @@ -130,7 +130,7 @@ func TestRegress(t *testing.T) { } */ -var regressGolden = []interface{}{ +var regressGolden = []any{ float64(0.5931317151369719), // ExpFloat64() float64(0.0680034588807843), // ExpFloat64() float64(0.036496967459790364), // ExpFloat64() diff --git a/gnovm/stdlibs/regexp/syntax/parse_test.gno b/gnovm/stdlibs/regexp/syntax/parse_test.gno index 0558b95bbe7..cd5def03427 100644 --- a/gnovm/stdlibs/regexp/syntax/parse_test.gno +++ b/gnovm/stdlibs/regexp/syntax/parse_test.gno @@ -319,7 +319,7 @@ var opNames = []string{ OpAlternate: "alt", } -func fmtFprintf(b io.Writer, format string, args ...interface{}) { +func fmtFprintf(b io.Writer, format string, args ...any) { res := fmt.Sprintf(format, args...) b.Write([]byte(res)) } diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 4c20e8d4b61..9db914e6a4f 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -5,7 +5,7 @@ import ( "strings" ) -// Realm functions can call std.GetBanker(options) to get +// Realm functions can call std.NewBanker(options) to get // a banker instance. Banker objects cannot be persisted, // but can be passed onto other functions to be transacted // on. A banker instance can be passed onto other realm @@ -28,7 +28,7 @@ type Banker interface { } // BankerType represents the "permission level" requested for a banker, -// retrievable through [GetBanker]. +// retrievable through [NewBanker]. type BankerType uint8 // Available types of banker. @@ -36,7 +36,7 @@ const ( // Can only read state. BankerTypeReadonly BankerType = iota // Can only send from tx send. - BankerTypeOrigSend + BankerTypeOriginSend // Can send from all realm coins. BankerTypeRealmSend // Can issue and remove realm coins. @@ -49,8 +49,8 @@ func (b BankerType) String() string { switch b { case BankerTypeReadonly: return "BankerTypeReadonly" - case BankerTypeOrigSend: - return "BankerTypeOrigSend" + case BankerTypeOriginSend: + return "BankerTypeOriginSend" case BankerTypeRealmSend: return "BankerTypeRealmSend" case BankerTypeRealmIssue: @@ -63,22 +63,22 @@ func (b BankerType) String() string { //---------------------------------------- // adapter for native banker -// GetBanker returns a new Banker, with its capabilities matching the given +// NewBanker returns a new Banker, with its capabilities matching the given // [BankerType]. -func GetBanker(bt BankerType) Banker { +func NewBanker(bt BankerType) Banker { assertCallerIsRealm() if bt >= maxBanker { panic("invalid banker type") } var pkgAddr Address - if bt == BankerTypeOrigSend { - pkgAddr = GetOrigPkgAddr() - if pkgAddr != CurrentRealm().Addr() { - panic("banker with type BankerTypeOrigSend can only be instantiated by the origin package") + if bt == BankerTypeOriginSend { + pkgAddr = OriginPkgAddress() + if pkgAddr != CurrentRealm().Address() { + panic("banker with type BankerTypeOriginSend can only be instantiated by the origin package") } } else if bt == BankerTypeRealmSend || bt == BankerTypeRealmIssue { - pkgAddr = CurrentRealm().Addr() + pkgAddr = CurrentRealm().Address() } return banker{ bt, diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index c57ba8529ed..ee02b0cfad0 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -25,7 +25,7 @@ const ( // Can only read state. btReadonly uint8 = iota //nolint // Can only send from tx send. - btOrigSend + btOriginSend // Can send from all realm coins. btRealmSend // Can issue and remove realm coins. @@ -45,19 +45,19 @@ func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []str from, to := crypto.Bech32Address(fromS), crypto.Bech32Address(toS) switch bt { - case btOrigSend: + case btOriginSend: // indirection allows us to "commit" in a second phase - spent := (*ctx.OrigSendSpent).Add(amt) - if !ctx.OrigSend.IsAllGTE(spent) { + spent := (*ctx.OriginSendSpent).Add(amt) + if !ctx.OriginSend.IsAllGTE(spent) { m.Panic(typedString( fmt.Sprintf( `cannot send "%v", limit "%v" exceeded with "%v" already spent`, - amt, ctx.OrigSend, *ctx.OrigSendSpent), + amt, ctx.OriginSend, *ctx.OriginSendSpent), )) return } ctx.Banker.SendCoins(from, to, amt) - *ctx.OrigSendSpent = spent + *ctx.OriginSendSpent = spent case btRealmSend, btRealmIssue: ctx.Banker.SendCoins(from, to, amt) default: diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index a8ef500c346..37d246b0f1a 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -8,18 +8,18 @@ import ( ) type ExecContext struct { - ChainID string - ChainDomain string - Height int64 - Timestamp int64 // seconds - TimestampNano int64 // nanoseconds, only used for testing. - OrigCaller crypto.Bech32Address - OrigPkgAddr crypto.Bech32Address - OrigSend std.Coins - OrigSendSpent *std.Coins // mutable - Banker BankerInterface - Params ParamsInterface - EventLogger *sdk.EventLogger + ChainID string + ChainDomain string + Height int64 + Timestamp int64 // seconds + TimestampNano int64 // nanoseconds, only used for testing. + OriginCaller crypto.Bech32Address + OriginPkgAddr crypto.Bech32Address + OriginSend std.Coins + OriginSendSpent *std.Coins // mutable + Banker BankerInterface + Params ParamsInterface + EventLogger *sdk.EventLogger } // GetContext returns the execution context. diff --git a/gnovm/stdlibs/std/emit_event.go b/gnovm/stdlibs/std/emit_event.go index 10b00d62cfc..6e8f00ee3e8 100644 --- a/gnovm/stdlibs/std/emit_event.go +++ b/gnovm/stdlibs/std/emit_event.go @@ -18,7 +18,7 @@ func X_emit(m *gno.Machine, typ string, attrs []string) { } _, pkgPath := currentRealm(m) - fnIdent := getPrevFunctionNameFromTarget(m, "Emit") + fnIdent := getPreviousFunctionNameFromTarget(m, "Emit") ctx := GetContext(m) diff --git a/gnovm/stdlibs/std/frame.gno b/gnovm/stdlibs/std/frame.gno index 1709f8cb8b5..bcffa458043 100644 --- a/gnovm/stdlibs/std/frame.gno +++ b/gnovm/stdlibs/std/frame.gno @@ -5,7 +5,7 @@ type Realm struct { pkgPath string } -func (r Realm) Addr() Address { +func (r Realm) Address() Address { return r.addr } diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 9cf8808a07e..1b15bc21c29 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -1,21 +1,17 @@ package std -// AssertOriginCall panics if [IsOriginCall] returns false. -func AssertOriginCall() // injected - -// IsOriginCall returns true only if the calling method is invoked via a direct -// MsgCall. It returns false for all other cases, like if the calling method +// AssertOriginCall panics if the calling method is not invoked via a direct +// MsgCall. It panics for for other cases, like if the calling method // is invoked by another method (even from the same realm or package). -// It also returns false every time when the transaction is broadcasted via +// It also panic every time when the transaction is broadcasted via // MsgRun. -func IsOriginCall() bool // injected - -func GetChainID() string // injected -func GetChainDomain() string // injected -func GetHeight() int64 // injected +func AssertOriginCall() // injected +func ChainID() string // injected +func ChainDomain() string // injected +func ChainHeight() int64 // injected -func GetOrigSend() Coins { - den, amt := origSend() +func OriginSend() Coins { + den, amt := originSend() coins := make(Coins, len(den)) for i := range coins { coins[i] = Coin{Denom: den[i], Amount: amt[i]} @@ -23,8 +19,8 @@ func GetOrigSend() Coins { return coins } -func GetOrigCaller() Address { - return Address(origCaller()) +func OriginCaller() Address { + return Address(originCaller()) } func CurrentRealm() Realm { @@ -32,23 +28,23 @@ func CurrentRealm() Realm { return Realm{Address(addr), path} } -func PrevRealm() Realm { +func PreviousRealm() Realm { addr, path := getRealm(1) return Realm{Address(addr), path} } -func GetOrigPkgAddr() Address { - return Address(origPkgAddr()) +func OriginPkgAddress() Address { + return Address(originPkgAddr()) } -func GetCallerAt(n int) Address { +func CallerAt(n int) Address { return Address(callerAt(n)) } // Variations which don't use named types. -func origSend() (denoms []string, amounts []int64) -func origCaller() string -func origPkgAddr() string +func originSend() (denoms []string, amounts []int64) +func originCaller() string +func originPkgAddr() string func callerAt(n int) string func getRealm(height int) (address string, pkgPath string) func assertCallerIsRealm() diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 9e398e907a2..b226ca03afa 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -7,12 +7,12 @@ import ( ) func AssertOriginCall(m *gno.Machine) { - if !IsOriginCall(m) { + if !isOriginCall(m) { m.Panic(typedString("invalid non-origin call")) } } -func IsOriginCall(m *gno.Machine) bool { +func isOriginCall(m *gno.Machine) bool { n := m.NumFrames() if n == 0 { return false @@ -22,29 +22,29 @@ func IsOriginCall(m *gno.Machine) bool { return n <= 2 && isMsgCall } -func GetChainID(m *gno.Machine) string { +func ChainID(m *gno.Machine) string { return GetContext(m).ChainID } -func GetChainDomain(m *gno.Machine) string { +func ChainDomain(m *gno.Machine) string { return GetContext(m).ChainDomain } -func GetHeight(m *gno.Machine) int64 { +func ChainHeight(m *gno.Machine) int64 { return GetContext(m).Height } -// getPrevFunctionNameFromTarget returns the last called function name (identifier) from the call stack. -func getPrevFunctionNameFromTarget(m *gno.Machine, targetFunc string) string { - targetIndex := findTargetFuncIndex(m, targetFunc) +// getPreviousFunctionNameFromTarget returns the last called function name (identifier) from the call stack. +func getPreviousFunctionNameFromTarget(m *gno.Machine, targetFunc string) string { + targetIndex := findTargetFunctionIndex(m, targetFunc) if targetIndex == -1 { return "" } - return findPrevFuncName(m, targetIndex) + return findPreviousFunctionName(m, targetIndex) } -// findTargetFuncIndex finds and returns the index of the target function in the call stack. -func findTargetFuncIndex(m *gno.Machine, targetFunc string) int { +// findTargetFunctionIndex finds and returns the index of the target function in the call stack. +func findTargetFunctionIndex(m *gno.Machine, targetFunc string) int { for i := len(m.Frames) - 1; i >= 0; i-- { currFunc := m.Frames[i].Func if currFunc != nil && currFunc.Name == gno.Name(targetFunc) { @@ -54,8 +54,8 @@ func findTargetFuncIndex(m *gno.Machine, targetFunc string) int { return -1 } -// findPrevFuncName returns the function name before the given index in the call stack. -func findPrevFuncName(m *gno.Machine, targetIndex int) string { +// findPreviousFunctionName returns the function name before the given index in the call stack. +func findPreviousFunctionName(m *gno.Machine, targetIndex int) string { for i := targetIndex - 1; i >= 0; i-- { currFunc := m.Frames[i].Func if currFunc != nil { @@ -66,25 +66,25 @@ func findPrevFuncName(m *gno.Machine, targetIndex int) string { panic("function name not found") } -func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { - os := GetContext(m).OrigSend +func X_originSend(m *gno.Machine) (denoms []string, amounts []int64) { + os := GetContext(m).OriginSend return ExpandCoins(os) } -func X_origCaller(m *gno.Machine) string { - return string(GetContext(m).OrigCaller) +func X_originCaller(m *gno.Machine) string { + return string(GetContext(m).OriginCaller) } -func X_origPkgAddr(m *gno.Machine) string { - return string(GetContext(m).OrigPkgAddr) +func X_originPkgAddr(m *gno.Machine) string { + return string(GetContext(m).OriginPkgAddr) } func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { - m.Panic(typedString("GetCallerAt requires positive arg")) + m.Panic(typedString("CallerAt requires positive arg")) return "" } - // Add 1 to n to account for the GetCallerAt (gno fn) frame. + // Add 1 to n to account for the CallerAt (gno fn) frame. n++ if n > m.NumFrames() { // NOTE: the last frame's LastPackage @@ -94,9 +94,9 @@ func X_callerAt(m *gno.Machine, n int) string { return "" } if n == m.NumFrames() { - // This makes it consistent with GetOrigCaller. + // This makes it consistent with OriginCaller. ctx := GetContext(m) - return string(ctx.OrigCaller) + return string(ctx.OriginCaller) } return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } @@ -131,8 +131,8 @@ func X_getRealm(m *gno.Machine, height int) (address, pkgPath string) { } } - // Fallback case: return OrigCaller. - return string(ctx.OrigCaller), "" + // Fallback case: return OriginCaller. + return string(ctx.OriginCaller), "" } // currentRealm retrieves the current realm's address and pkgPath. diff --git a/gnovm/stdlibs/std/native_test.go b/gnovm/stdlibs/std/native_test.go index 851785575d7..e51badd4e2e 100644 --- a/gnovm/stdlibs/std/native_test.go +++ b/gnovm/stdlibs/std/native_test.go @@ -9,11 +9,11 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" ) -func TestPrevRealmIsOrigin(t *testing.T) { +func TestPreviousRealmIsOrigin(t *testing.T) { var ( user = gno.DerivePkgAddr("user1.gno").Bech32() ctx = ExecContext{ - OrigCaller: user, + OriginCaller: user, } msgCallFrame = &gno.Frame{LastPackage: &gno.PackageValue{PkgPath: "main"}} msgRunFrame = &gno.Frame{LastPackage: &gno.PackageValue{PkgPath: "gno.land/r/g1337/run"}} @@ -184,7 +184,7 @@ func TestPrevRealmIsOrigin(t *testing.T) { assert := assert.New(t) addr, pkgPath := X_getRealm(tt.machine, 1) - isOrigin := IsOriginCall(tt.machine) + isOrigin := isOriginCall(tt.machine) assert.Equal(string(tt.expectedAddr), addr) assert.Equal(tt.expectedPkgPath, pkgPath) diff --git a/gnovm/stdlibs/std/params.gno b/gnovm/stdlibs/std/params.gno index ce400270cda..e7691471227 100644 --- a/gnovm/stdlibs/std/params.gno +++ b/gnovm/stdlibs/std/params.gno @@ -5,9 +5,13 @@ func setParamBool(key string, val bool) func setParamInt64(key string, val int64) func setParamUint64(key string, val uint64) func setParamBytes(key string, val []byte) +func setParamStrings(key string, val []string) -func SetParamString(key string, val string) { setParamString(key, val) } -func SetParamBool(key string, val bool) { setParamBool(key, val) } -func SetParamInt64(key string, val int64) { setParamInt64(key, val) } -func SetParamUint64(key string, val uint64) { setParamUint64(key, val) } -func SetParamBytes(key string, val []byte) { setParamBytes(key, val) } +// SetParamXXX(k, v) are for setting arbitrary realm-local parameters that can be called from any realm. +// It may or may not affect system behavior. +func SetParamString(key string, val string) { setParamString(key, val) } +func SetParamBool(key string, val bool) { setParamBool(key, val) } +func SetParamInt64(key string, val int64) { setParamInt64(key, val) } +func SetParamUint64(key string, val uint64) { setParamUint64(key, val) } +func SetParamBytes(key string, val []byte) { setParamBytes(key, val) } +func SetParamStrings(key string, val []string) { setParamStrings(key, val) } diff --git a/gnovm/stdlibs/std/params.go b/gnovm/stdlibs/std/params.go index e21bd9912dd..cc2885045fc 100644 --- a/gnovm/stdlibs/std/params.go +++ b/gnovm/stdlibs/std/params.go @@ -3,70 +3,61 @@ package std import ( "fmt" "strings" - "unicode" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" ) -// ParamsInterface is the interface through which Gno is capable of accessing -// the blockchain's params. -// -// The name is what it is to avoid a collision with Gno's Params, when -// transpiling. +// std.SetParam*() can only be used to set realm-local VM parameters. All +// parameters stored in ExecContext.Params will be prefixed by "vm::". +// TODO rename to SetRealmParam*(). + type ParamsInterface interface { SetString(key, val string) SetBool(key string, val bool) SetInt64(key string, val int64) SetUint64(key string, val uint64) SetBytes(key string, val []byte) + SetStrings(key string, val []string) } func X_setParamString(m *gno.Machine, key, val string) { - pk := pkey(m, key, "string") + pk := pkey(m, key) GetContext(m).Params.SetString(pk, val) } func X_setParamBool(m *gno.Machine, key string, val bool) { - pk := pkey(m, key, "bool") + pk := pkey(m, key) GetContext(m).Params.SetBool(pk, val) } func X_setParamInt64(m *gno.Machine, key string, val int64) { - pk := pkey(m, key, "int64") + pk := pkey(m, key) GetContext(m).Params.SetInt64(pk, val) } func X_setParamUint64(m *gno.Machine, key string, val uint64) { - pk := pkey(m, key, "uint64") + pk := pkey(m, key) GetContext(m).Params.SetUint64(pk, val) } func X_setParamBytes(m *gno.Machine, key string, val []byte) { - pk := pkey(m, key, "bytes") + pk := pkey(m, key) GetContext(m).Params.SetBytes(pk, val) } -func pkey(m *gno.Machine, key string, kind string) string { - // validate key. - untypedKey := strings.TrimSuffix(key, "."+kind) - if key == untypedKey { - m.Panic(typedString("invalid param key: " + key)) - } +func X_setParamStrings(m *gno.Machine, key string, val []string) { + pk := pkey(m, key) + GetContext(m).Params.SetStrings(pk, val) +} +// NOTE: further validation must happen by implementor of ParamsInterface. +func pkey(m *gno.Machine, key string) string { if len(key) == 0 { m.Panic(typedString("empty param key")) } - first := rune(key[0]) - if !unicode.IsLetter(first) && first != '_' { + if strings.Contains(key, ":") { m.Panic(typedString("invalid param key: " + key)) } - for _, char := range untypedKey[1:] { - if !unicode.IsLetter(char) && !unicode.IsDigit(char) && char != '_' { - m.Panic(typedString("invalid param key: " + key)) - } - } - - // decorate key with realm and type. _, rlmPath := currentRealm(m) - return fmt.Sprintf("%s.%s", rlmPath, key) + return fmt.Sprintf("vm:%s:%s", rlmPath, key) } diff --git a/gnovm/stdlibs/std/params_test.go b/gnovm/stdlibs/std/params_test.go new file mode 100644 index 00000000000..21de716e69b --- /dev/null +++ b/gnovm/stdlibs/std/params_test.go @@ -0,0 +1,59 @@ +package std + +import ( + "testing" +) + +// XXX move elsewhere in the gno.land/ dir. + +func TestValidate(t *testing.T) { + t.Skip("this test isn't testing useful things. we need to move it to the good location and make sure to use an appropriate validate() function.") + tests := []struct { + module string + submodule string + name string + type_ string + wantErr bool + }{ + // Valid cases + {"module", "p", "valid_key", "string", false}, + {"module", "p", "valid_key.string", "string", false}, // is a string + {"module1", "p", "validKey", "int64", false}, + {"module", "p", "1invalid", "string", false}, // Starts with a number (see IsASCII) + {"p_", "p", "_valid123", "bool", false}, + {"module", "_", "valid_key", "string", false}, // Underscore as submodule + {"module", "gno.land/r/myuser/myrealm", "valid_key", "string", false}, // Realm path as submodule + {"1module", "p", "valid_key.string", "string", false}, // Module starts with a number + + // Invalid key cases + {"module", "p", "", "string", true}, // Empty key + {"module", "p", "-invalid", "string", true}, // Starts with an invalid character + {"module", "p", "invalid-123", "string", true}, // Contains invalid character (-) + {"module", "p", "valid/path.key", "bool", true}, // Contains invalid character (/) + {"module", "p", "invalid.string", "int64", true}, // Not a string + {"module", "p", "valid:key", "string", true}, // ":" in name + {"module", "p", "valid:", "string", true}, // ":" in name + + // Invalid submodule cases + {"module", "", "valid_key", "string", true}, // Empty submodule + {"module", "p:q", "valid_key", "string", true}, // ":" in submodule + {"module", "p:", "valid_key", "string", true}, // ":" in submodule + + // Invalid module cases + {"module!", "p", "valid_key", "string", true}, // Module contains invalid character (!) + {"-prefix", "p", "valid_key", "string", true}, // Module starts with an invalid character + {"module/submodule", "p", "valid_key", "string", true}, // Module contains invalid character (/) + {"module:submodule", "p", "valid_key", "string", true}, // Module contains invalid character (/) + } + + for _, tt := range tests { + validate := func(module, submodule, name, type_ string) error { + // FIXME: use a real module key validator. + return nil + } + err := validate(tt.module, tt.submodule, tt.name, tt.type_) + if (err != nil) != tt.wantErr { + t.Errorf("validate(%q, %q, %q, %q) = %v, wantErr %v", tt.module, tt.submodule, tt.name, tt.type_, err, tt.wantErr) + } + } +} diff --git a/gnovm/stdlibs/strconv/atob_test.gno b/gnovm/stdlibs/strconv/atob_test.gno index 39746f8953d..6e6d34e8320 100644 --- a/gnovm/stdlibs/strconv/atob_test.gno +++ b/gnovm/stdlibs/strconv/atob_test.gno @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "bytes" + "strconv" "testing" ) @@ -16,8 +17,8 @@ type atobTest struct { } var atobtests = []atobTest{ - {"", false, ErrSyntax}, - {"asdf", false, ErrSyntax}, + {"", false, strconv.ErrSyntax}, + {"asdf", false, strconv.ErrSyntax}, {"0", false, nil}, {"f", false, nil}, {"F", false, nil}, @@ -34,14 +35,14 @@ var atobtests = []atobTest{ func TestParseBool(t *testing.T) { for _, test := range atobtests { - b, e := ParseBool(test.in) + b, e := strconv.ParseBool(test.in) if test.err != nil { // expect an error if e == nil { t.Errorf("ParseBool(%s) = nil; want %s", test.in, test.err) } else { // NumError assertion must succeed; it's the only thing we return. - if e.(*NumError).Err != test.err { + if e.(*strconv.NumError).Err != test.err { t.Errorf("ParseBool(%s) = %s; want %s", test.in, e, test.err) } } @@ -63,7 +64,7 @@ var boolString = map[bool]string{ func TestFormatBool(t *testing.T) { for b, s := range boolString { - if f := FormatBool(b); f != s { + if f := strconv.FormatBool(b); f != s { t.Errorf("FormatBool(%v) = %q; want %q", b, f, s) } } @@ -82,7 +83,7 @@ var appendBoolTests = []appendBoolTest{ func TestAppendBool(t *testing.T) { for _, test := range appendBoolTests { - b := AppendBool(test.in, test.b) + b := strconv.AppendBool(test.in, test.b) if !bytes.Equal(b, test.out) { t.Errorf("AppendBool(%q, %v) = %q; want %q", test.in, test.b, b, test.out) } diff --git a/gnovm/stdlibs/strconv/atof_test.gno b/gnovm/stdlibs/strconv/atof_test.gno index 29d9e4e2f4b..63cf659018b 100644 --- a/gnovm/stdlibs/strconv/atof_test.gno +++ b/gnovm/stdlibs/strconv/atof_test.gno @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test // XXX: changed not to use dot imports import ( "math" "math/rand" + "strconv" "strings" "testing" ) @@ -20,11 +21,11 @@ type atofTest struct { } var atoftests = []atofTest{ - {"", "0", ErrSyntax}, + {"", "0", strconv.ErrSyntax}, {"1", "1", nil}, {"+1", "1", nil}, - {"1x", "0", ErrSyntax}, - {"1.1.", "0", ErrSyntax}, + {"1x", "0", strconv.ErrSyntax}, + {"1.1.", "0", strconv.ErrSyntax}, {"1e23", "1e+23", nil}, {"1E23", "1e+23", nil}, {"100000000000000000000000", "1e+23", nil}, @@ -55,8 +56,8 @@ var atoftests = []atofTest{ {"-0x2p3", "-16", nil}, {"0x0.fp4", "15", nil}, {"0x0.fp0", "0.9375", nil}, - {"0x1e2", "0", ErrSyntax}, - {"1p2", "0", ErrSyntax}, + {"0x1e2", "0", strconv.ErrSyntax}, + {"1p2", "0", strconv.ErrSyntax}, // zeros {"0", "0", nil}, @@ -131,16 +132,16 @@ var atoftests = []atofTest{ {"-0x.1fffffffffffffp1027", "-1.7976931348623157e+308", nil}, // next float64 - too large - {"1.7976931348623159e308", "+Inf", ErrRange}, - {"-1.7976931348623159e308", "-Inf", ErrRange}, - {"0x1p1024", "+Inf", ErrRange}, - {"-0x1p1024", "-Inf", ErrRange}, - {"0x2p1023", "+Inf", ErrRange}, - {"-0x2p1023", "-Inf", ErrRange}, - {"0x.1p1028", "+Inf", ErrRange}, - {"-0x.1p1028", "-Inf", ErrRange}, - {"0x.2p1027", "+Inf", ErrRange}, - {"-0x.2p1027", "-Inf", ErrRange}, + {"1.7976931348623159e308", "+Inf", strconv.ErrRange}, + {"-1.7976931348623159e308", "-Inf", strconv.ErrRange}, + {"0x1p1024", "+Inf", strconv.ErrRange}, + {"-0x1p1024", "-Inf", strconv.ErrRange}, + {"0x2p1023", "+Inf", strconv.ErrRange}, + {"-0x2p1023", "-Inf", strconv.ErrRange}, + {"0x.1p1028", "+Inf", strconv.ErrRange}, + {"-0x.1p1028", "-Inf", strconv.ErrRange}, + {"0x.2p1027", "+Inf", strconv.ErrRange}, + {"-0x.2p1027", "-Inf", strconv.ErrRange}, // the border is ...158079 // borderline - okay @@ -149,34 +150,34 @@ var atoftests = []atofTest{ {"0x1.fffffffffffff7fffp1023", "1.7976931348623157e+308", nil}, {"-0x1.fffffffffffff7fffp1023", "-1.7976931348623157e+308", nil}, // borderline - too large - {"1.797693134862315808e308", "+Inf", ErrRange}, - {"-1.797693134862315808e308", "-Inf", ErrRange}, - {"0x1.fffffffffffff8p1023", "+Inf", ErrRange}, - {"-0x1.fffffffffffff8p1023", "-Inf", ErrRange}, - {"0x1fffffffffffff.8p+971", "+Inf", ErrRange}, - {"-0x1fffffffffffff8p+967", "-Inf", ErrRange}, - {"0x.1fffffffffffff8p1027", "+Inf", ErrRange}, - {"-0x.1fffffffffffff9p1027", "-Inf", ErrRange}, + {"1.797693134862315808e308", "+Inf", strconv.ErrRange}, + {"-1.797693134862315808e308", "-Inf", strconv.ErrRange}, + {"0x1.fffffffffffff8p1023", "+Inf", strconv.ErrRange}, + {"-0x1.fffffffffffff8p1023", "-Inf", strconv.ErrRange}, + {"0x1fffffffffffff.8p+971", "+Inf", strconv.ErrRange}, + {"-0x1fffffffffffff8p+967", "-Inf", strconv.ErrRange}, + {"0x.1fffffffffffff8p1027", "+Inf", strconv.ErrRange}, + {"-0x.1fffffffffffff9p1027", "-Inf", strconv.ErrRange}, // a little too large {"1e308", "1e+308", nil}, - {"2e308", "+Inf", ErrRange}, - {"1e309", "+Inf", ErrRange}, - {"0x1p1025", "+Inf", ErrRange}, + {"2e308", "+Inf", strconv.ErrRange}, + {"1e309", "+Inf", strconv.ErrRange}, + {"0x1p1025", "+Inf", strconv.ErrRange}, // way too large - {"1e310", "+Inf", ErrRange}, - {"-1e310", "-Inf", ErrRange}, - {"1e400", "+Inf", ErrRange}, - {"-1e400", "-Inf", ErrRange}, - {"1e400000", "+Inf", ErrRange}, - {"-1e400000", "-Inf", ErrRange}, - {"0x1p1030", "+Inf", ErrRange}, - {"0x1p2000", "+Inf", ErrRange}, - {"0x1p2000000000", "+Inf", ErrRange}, - {"-0x1p1030", "-Inf", ErrRange}, - {"-0x1p2000", "-Inf", ErrRange}, - {"-0x1p2000000000", "-Inf", ErrRange}, + {"1e310", "+Inf", strconv.ErrRange}, + {"-1e310", "-Inf", strconv.ErrRange}, + {"1e400", "+Inf", strconv.ErrRange}, + {"-1e400", "-Inf", strconv.ErrRange}, + {"1e400000", "+Inf", strconv.ErrRange}, + {"-1e400000", "-Inf", strconv.ErrRange}, + {"0x1p1030", "+Inf", strconv.ErrRange}, + {"0x1p2000", "+Inf", strconv.ErrRange}, + {"0x1p2000000000", "+Inf", strconv.ErrRange}, + {"-0x1p1030", "-Inf", strconv.ErrRange}, + {"-0x1p2000", "-Inf", strconv.ErrRange}, + {"-0x1p2000000000", "-Inf", strconv.ErrRange}, // denormalized {"1e-305", "1e-305", nil}, @@ -238,29 +239,29 @@ var atoftests = []atofTest{ // try to overflow exponent {"1e-4294967296", "0", nil}, - {"1e+4294967296", "+Inf", ErrRange}, + {"1e+4294967296", "+Inf", strconv.ErrRange}, {"1e-18446744073709551616", "0", nil}, - {"1e+18446744073709551616", "+Inf", ErrRange}, + {"1e+18446744073709551616", "+Inf", strconv.ErrRange}, {"0x1p-4294967296", "0", nil}, - {"0x1p+4294967296", "+Inf", ErrRange}, + {"0x1p+4294967296", "+Inf", strconv.ErrRange}, {"0x1p-18446744073709551616", "0", nil}, - {"0x1p+18446744073709551616", "+Inf", ErrRange}, + {"0x1p+18446744073709551616", "+Inf", strconv.ErrRange}, // Parse errors - {"1e", "0", ErrSyntax}, - {"1e-", "0", ErrSyntax}, - {".e-1", "0", ErrSyntax}, - {"1\x00.2", "0", ErrSyntax}, - {"0x", "0", ErrSyntax}, - {"0x.", "0", ErrSyntax}, - {"0x1", "0", ErrSyntax}, - {"0x.1", "0", ErrSyntax}, - {"0x1p", "0", ErrSyntax}, - {"0x.1p", "0", ErrSyntax}, - {"0x1p+", "0", ErrSyntax}, - {"0x.1p+", "0", ErrSyntax}, - {"0x1p-", "0", ErrSyntax}, - {"0x.1p-", "0", ErrSyntax}, + {"1e", "0", strconv.ErrSyntax}, + {"1e-", "0", strconv.ErrSyntax}, + {".e-1", "0", strconv.ErrSyntax}, + {"1\x00.2", "0", strconv.ErrSyntax}, + {"0x", "0", strconv.ErrSyntax}, + {"0x.", "0", strconv.ErrSyntax}, + {"0x1", "0", strconv.ErrSyntax}, + {"0x.1", "0", strconv.ErrSyntax}, + {"0x1p", "0", strconv.ErrSyntax}, + {"0x.1p", "0", strconv.ErrSyntax}, + {"0x1p+", "0", strconv.ErrSyntax}, + {"0x.1p+", "0", strconv.ErrSyntax}, + {"0x1p-", "0", strconv.ErrSyntax}, + {"0x.1p-", "0", strconv.ErrSyntax}, {"0x1p+2", "4", nil}, {"0x.1p+2", "0.25", nil}, {"0x1p-2", "0.25", nil}, @@ -309,40 +310,40 @@ var atoftests = []atofTest{ // Underscores. {"1_23.50_0_0e+1_2", "1.235e+14", nil}, - {"-_123.5e+12", "0", ErrSyntax}, - {"+_123.5e+12", "0", ErrSyntax}, - {"_123.5e+12", "0", ErrSyntax}, - {"1__23.5e+12", "0", ErrSyntax}, - {"123_.5e+12", "0", ErrSyntax}, - {"123._5e+12", "0", ErrSyntax}, - {"123.5_e+12", "0", ErrSyntax}, - {"123.5__0e+12", "0", ErrSyntax}, - {"123.5e_+12", "0", ErrSyntax}, - {"123.5e+_12", "0", ErrSyntax}, - {"123.5e_-12", "0", ErrSyntax}, - {"123.5e-_12", "0", ErrSyntax}, - {"123.5e+1__2", "0", ErrSyntax}, - {"123.5e+12_", "0", ErrSyntax}, + {"-_123.5e+12", "0", strconv.ErrSyntax}, + {"+_123.5e+12", "0", strconv.ErrSyntax}, + {"_123.5e+12", "0", strconv.ErrSyntax}, + {"1__23.5e+12", "0", strconv.ErrSyntax}, + {"123_.5e+12", "0", strconv.ErrSyntax}, + {"123._5e+12", "0", strconv.ErrSyntax}, + {"123.5_e+12", "0", strconv.ErrSyntax}, + {"123.5__0e+12", "0", strconv.ErrSyntax}, + {"123.5e_+12", "0", strconv.ErrSyntax}, + {"123.5e+_12", "0", strconv.ErrSyntax}, + {"123.5e_-12", "0", strconv.ErrSyntax}, + {"123.5e-_12", "0", strconv.ErrSyntax}, + {"123.5e+1__2", "0", strconv.ErrSyntax}, + {"123.5e+12_", "0", strconv.ErrSyntax}, {"0x_1_2.3_4_5p+1_2", "74565", nil}, - {"-_0x12.345p+12", "0", ErrSyntax}, - {"+_0x12.345p+12", "0", ErrSyntax}, - {"_0x12.345p+12", "0", ErrSyntax}, - {"0x__12.345p+12", "0", ErrSyntax}, - {"0x1__2.345p+12", "0", ErrSyntax}, - {"0x12_.345p+12", "0", ErrSyntax}, - {"0x12._345p+12", "0", ErrSyntax}, - {"0x12.3__45p+12", "0", ErrSyntax}, - {"0x12.345_p+12", "0", ErrSyntax}, - {"0x12.345p_+12", "0", ErrSyntax}, - {"0x12.345p+_12", "0", ErrSyntax}, - {"0x12.345p_-12", "0", ErrSyntax}, - {"0x12.345p-_12", "0", ErrSyntax}, - {"0x12.345p+1__2", "0", ErrSyntax}, - {"0x12.345p+12_", "0", ErrSyntax}, - - {"1e100x", "0", ErrSyntax}, - {"1e1000x", "0", ErrSyntax}, + {"-_0x12.345p+12", "0", strconv.ErrSyntax}, + {"+_0x12.345p+12", "0", strconv.ErrSyntax}, + {"_0x12.345p+12", "0", strconv.ErrSyntax}, + {"0x__12.345p+12", "0", strconv.ErrSyntax}, + {"0x1__2.345p+12", "0", strconv.ErrSyntax}, + {"0x12_.345p+12", "0", strconv.ErrSyntax}, + {"0x12._345p+12", "0", strconv.ErrSyntax}, + {"0x12.3__45p+12", "0", strconv.ErrSyntax}, + {"0x12.345_p+12", "0", strconv.ErrSyntax}, + {"0x12.345p_+12", "0", strconv.ErrSyntax}, + {"0x12.345p+_12", "0", strconv.ErrSyntax}, + {"0x12.345p_-12", "0", strconv.ErrSyntax}, + {"0x12.345p-_12", "0", strconv.ErrSyntax}, + {"0x12.345p+1__2", "0", strconv.ErrSyntax}, + {"0x12.345p+12_", "0", strconv.ErrSyntax}, + + {"1e100x", "0", strconv.ErrSyntax}, + {"1e1000x", "0", strconv.ErrSyntax}, } var atof32tests = []atofTest{ @@ -374,10 +375,10 @@ var atof32tests = []atofTest{ {"-340282346638528859811704183484516925440", "-3.4028235e+38", nil}, {"-0x.ffffffp128", "-3.4028235e+38", nil}, // next float32 - too large - {"3.4028236e38", "+Inf", ErrRange}, - {"-3.4028236e38", "-Inf", ErrRange}, - {"0x1.0p128", "+Inf", ErrRange}, - {"-0x1.0p128", "-Inf", ErrRange}, + {"3.4028236e38", "+Inf", strconv.ErrRange}, + {"-3.4028236e38", "-Inf", strconv.ErrRange}, + {"0x1.0p128", "+Inf", strconv.ErrRange}, + {"-0x1.0p128", "-Inf", strconv.ErrRange}, // the border is 3.40282356779...e+38 // borderline - okay {"3.402823567e38", "3.4028235e+38", nil}, @@ -385,10 +386,10 @@ var atof32tests = []atofTest{ {"0x.ffffff7fp128", "3.4028235e+38", nil}, {"-0x.ffffff7fp128", "-3.4028235e+38", nil}, // borderline - too large - {"3.4028235678e38", "+Inf", ErrRange}, - {"-3.4028235678e38", "-Inf", ErrRange}, - {"0x.ffffff8p128", "+Inf", ErrRange}, - {"-0x.ffffff8p128", "-Inf", ErrRange}, + {"3.4028235678e38", "+Inf", strconv.ErrRange}, + {"-3.4028235678e38", "-Inf", strconv.ErrRange}, + {"0x.ffffff8p128", "+Inf", strconv.ErrRange}, + {"-0x.ffffff8p128", "-Inf", strconv.ErrRange}, // Denormals: less than 2^-126 {"1e-38", "1e-38", nil}, @@ -454,13 +455,13 @@ func initAtofOnce() { for i := range atoftests { test := &atoftests[i] if test.err != nil { - test.err = &NumError{"ParseFloat", test.in, test.err} + test.err = &strconv.NumError{"ParseFloat", test.in, test.err} } } for i := range atof32tests { test := &atof32tests[i] if test.err != nil { - test.err = &NumError{"ParseFloat", test.in, test.err} + test.err = &strconv.NumError{"ParseFloat", test.in, test.err} } } @@ -473,19 +474,19 @@ func initAtofOnce() { for i := range atofRandomTests { n := uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) x := math.Float64frombits(n) - s := FormatFloat(x, 'g', -1, 64) + s := strconv.FormatFloat(x, 'g', -1, 64) atofRandomTests[i] = atofSimpleTest{x, s} } for i := range benchmarksRandomBits { bits := uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) x := math.Float64frombits(bits) - benchmarksRandomBits[i] = FormatFloat(x, 'g', -1, 64) + benchmarksRandomBits[i] = strconv.FormatFloat(x, 'g', -1, 64) } for i := range benchmarksRandomNormal { x := rand.NormFloat64() - benchmarksRandomNormal[i] = FormatFloat(x, 'g', -1, 64) + benchmarksRandomNormal[i] = strconv.FormatFloat(x, 'g', -1, 64) } } @@ -500,7 +501,7 @@ func TestParseFloatPrefix(t *testing.T) { // correctly as "inf" with suffix. for _, suffix := range []string{" ", "q", "+", "-", "<", "=", ">", "(", ")", "i", "init"} { in := test.in + suffix - _, n, err := ParseFloatPrefix(in, 64) + _, n, err := strconv.ParseFloatPrefix(in, 64) if err != nil { t.Errorf("ParseFloatPrefix(%q, 64): err = %v; want no error", in, err) } @@ -530,24 +531,24 @@ func printError(err error) string { func testAtof(t *testing.T, opt bool) { initAtof() - oldopt := SetOptimize(opt) + oldopt := strconv.SetOptimize(opt) for i := 0; i < len(atoftests); i++ { test := &atoftests[i] - out, err := ParseFloat(test.in, 64) - outs := FormatFloat(out, 'g', -1, 64) + out, err := strconv.ParseFloat(test.in, 64) + outs := strconv.FormatFloat(out, 'g', -1, 64) if outs != test.out || !errEqual(err, test.err) { t.Errorf("ParseFloat(%v, 64) = %v, %v want %v, %v", test.in, out, printError(err), test.out, printError(test.err)) } if float64(float32(out)) == out { - out, err := ParseFloat(test.in, 32) + out, err := strconv.ParseFloat(test.in, 32) out32 := float32(out) if float64(out32) != out { t.Errorf("ParseFloat(%v, 32) = %v, not a float32 (closest is %v)", test.in, out, float64(out32)) continue } - outs := FormatFloat(float64(out32), 'g', -1, 32) + outs := strconv.FormatFloat(float64(out32), 'g', -1, 32) if outs != test.out || !errEqual(err, test.err) { t.Errorf("ParseFloat(%v, 32) = %v, %v want %v, %v # %v", test.in, out32, printError(err), test.out, printError(test.err), out) @@ -555,19 +556,19 @@ func testAtof(t *testing.T, opt bool) { } } for _, test := range atof32tests { - out, err := ParseFloat(test.in, 32) + out, err := strconv.ParseFloat(test.in, 32) out32 := float32(out) if float64(out32) != out { t.Errorf("ParseFloat(%v, 32) = %v, not a float32 (closest is %v)", test.in, out, float64(out32)) continue } - outs := FormatFloat(float64(out32), 'g', -1, 32) + outs := strconv.FormatFloat(float64(out32), 'g', -1, 32) if outs != test.out || !errEqual(err, test.err) { t.Errorf("ParseFloat(%v, 32) = %v, %v want %v, %v # %v", test.in, out32, printError(err), test.out, printError(test.err), out) } } - SetOptimize(oldopt) + strconv.SetOptimize(oldopt) } func TestAtof(t *testing.T) { testAtof(t, true) } @@ -577,7 +578,7 @@ func TestAtofSlow(t *testing.T) { testAtof(t, false) } func TestAtofRandom(t *testing.T) { initAtof() for _, test := range atofRandomTests { - x, _ := ParseFloat(test.s, 64) + x, _ := strconv.ParseFloat(test.s, 64) switch { default: t.Errorf("number %s badly parsed as %b (expected %b)", test.s, x, test.x) @@ -604,25 +605,25 @@ var roundTripCases = []struct { func TestRoundTrip(t *testing.T) { for _, tt := range roundTripCases { - old := SetOptimize(false) - s := FormatFloat(tt.f, 'g', -1, 64) + old := strconv.SetOptimize(false) + s := strconv.FormatFloat(tt.f, 'g', -1, 64) if s != tt.s { t.Errorf("no-opt FormatFloat(%b) = %s, want %s", tt.f, s, tt.s) } - f, err := ParseFloat(tt.s, 64) + f, err := strconv.ParseFloat(tt.s, 64) if f != tt.f || err != nil { t.Errorf("no-opt ParseFloat(%s) = %b, %v want %b, nil", tt.s, f, err, tt.f) } - SetOptimize(true) - s = FormatFloat(tt.f, 'g', -1, 64) + strconv.SetOptimize(true) + s = strconv.FormatFloat(tt.f, 'g', -1, 64) if s != tt.s { t.Errorf("opt FormatFloat(%b) = %s, want %s", tt.f, s, tt.s) } - f, err = ParseFloat(tt.s, 64) + f, err = strconv.ParseFloat(tt.s, 64) if f != tt.f || err != nil { t.Errorf("opt ParseFloat(%s) = %b, %v want %b, nil", tt.s, f, err, tt.f) } - SetOptimize(old) + strconv.SetOptimize(old) } } @@ -638,9 +639,9 @@ func TestRoundTrip32(t *testing.T) { if i&1 == 1 { f = -f // negative } - s := FormatFloat(float64(f), 'g', -1, 32) + s := strconv.FormatFloat(float64(f), 'g', -1, 32) - parsed, err := ParseFloat(s, 32) + parsed, err := strconv.ParseFloat(s, 32) parsed32 := float32(parsed) switch { case err != nil: @@ -662,7 +663,7 @@ func TestParseFloatIncorrectBitSize(t *testing.T) { const want = 1.5e308 for _, bitSize := range []int{0, 10, 100, 128} { - f, err := ParseFloat(s, bitSize) + f, err := strconv.ParseFloat(s, bitSize) if err != nil { t.Fatalf("ParseFloat(%q, %d) gave error %s", s, bitSize, err) } @@ -674,25 +675,25 @@ func TestParseFloatIncorrectBitSize(t *testing.T) { func BenchmarkAtof64Decimal(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("33909", 64) + strconv.ParseFloat("33909", 64) } } func BenchmarkAtof64Float(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("339.7784", 64) + strconv.ParseFloat("339.7784", 64) } } func BenchmarkAtof64FloatExp(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("-5.09e75", 64) + strconv.ParseFloat("-5.09e75", 64) } } func BenchmarkAtof64Big(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("123456789123456789123456789", 64) + strconv.ParseFloat("123456789123456789123456789", 64) } } @@ -700,7 +701,7 @@ func BenchmarkAtof64RandomBits(b *testing.B) { initAtof() b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(benchmarksRandomBits[i%1024], 64) + strconv.ParseFloat(benchmarksRandomBits[i%1024], 64) } } @@ -708,7 +709,7 @@ func BenchmarkAtof64RandomFloats(b *testing.B) { initAtof() b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(benchmarksRandomNormal[i%1024], 64) + strconv.ParseFloat(benchmarksRandomNormal[i%1024], 64) } } @@ -716,12 +717,12 @@ func BenchmarkAtof64RandomLongFloats(b *testing.B) { initAtof() samples := make([]string, len(atofRandomTests)) for i, t := range atofRandomTests { - samples[i] = FormatFloat(t.x, 'g', 20, 64) + samples[i] = strconv.FormatFloat(t.x, 'g', 20, 64) } b.ResetTimer() idx := 0 for i := 0; i < b.N; i++ { - ParseFloat(samples[idx], 64) + strconv.ParseFloat(samples[idx], 64) idx++ if idx == len(samples) { idx = 0 @@ -731,19 +732,19 @@ func BenchmarkAtof64RandomLongFloats(b *testing.B) { func BenchmarkAtof32Decimal(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("33909", 32) + strconv.ParseFloat("33909", 32) } } func BenchmarkAtof32Float(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("339.778", 32) + strconv.ParseFloat("339.778", 32) } } func BenchmarkAtof32FloatExp(b *testing.B) { for i := 0; i < b.N; i++ { - ParseFloat("12.3456e32", 32) + strconv.ParseFloat("12.3456e32", 32) } } @@ -752,11 +753,11 @@ func BenchmarkAtof32Random(b *testing.B) { var float32strings [4096]string for i := range float32strings { n = (99991*n + 42) % (0xff << 23) - float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', -1, 32) + float32strings[i] = strconv.FormatFloat(float64(math.Float32frombits(n)), 'g', -1, 32) } b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(float32strings[i%4096], 32) + strconv.ParseFloat(float32strings[i%4096], 32) } } @@ -765,10 +766,10 @@ func BenchmarkAtof32RandomLong(b *testing.B) { var float32strings [4096]string for i := range float32strings { n = (99991*n + 42) % (0xff << 23) - float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', 20, 32) + float32strings[i] = strconv.FormatFloat(float64(math.Float32frombits(n)), 'g', 20, 32) } b.ResetTimer() for i := 0; i < b.N; i++ { - ParseFloat(float32strings[i%4096], 32) + strconv.ParseFloat(float32strings[i%4096], 32) } } diff --git a/gnovm/stdlibs/strconv/atoi_test.gno b/gnovm/stdlibs/strconv/atoi_test.gno index cb150628f5c..ad4e5e0528c 100644 --- a/gnovm/stdlibs/strconv/atoi_test.gno +++ b/gnovm/stdlibs/strconv/atoi_test.gno @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "errors" "fmt" + "strconv" "testing" ) @@ -17,23 +18,23 @@ type parseUint64Test struct { } var parseUint64Tests = []parseUint64Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"1", 1, nil}, {"12345", 12345, nil}, {"012345", 12345, nil}, - {"12345x", 0, ErrSyntax}, + {"12345x", 0, strconv.ErrSyntax}, {"98765432100", 98765432100, nil}, {"18446744073709551615", 1<<64 - 1, nil}, - {"18446744073709551616", 1<<64 - 1, ErrRange}, - {"18446744073709551620", 1<<64 - 1, ErrRange}, - {"1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, - {"-0", 0, ErrSyntax}, - {"-1", 0, ErrSyntax}, - {"+1", 0, ErrSyntax}, + {"18446744073709551616", 1<<64 - 1, strconv.ErrRange}, + {"18446744073709551620", 1<<64 - 1, strconv.ErrRange}, + {"1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, + {"-0", 0, strconv.ErrSyntax}, + {"-1", 0, strconv.ErrSyntax}, + {"+1", 0, strconv.ErrSyntax}, } type parseUint64BaseTest struct { @@ -44,91 +45,91 @@ type parseUint64BaseTest struct { } var parseUint64BaseTests = []parseUint64BaseTest{ - {"", 0, 0, ErrSyntax}, + {"", 0, 0, strconv.ErrSyntax}, {"0", 0, 0, nil}, - {"0x", 0, 0, ErrSyntax}, - {"0X", 0, 0, ErrSyntax}, + {"0x", 0, 0, strconv.ErrSyntax}, + {"0X", 0, 0, strconv.ErrSyntax}, {"1", 0, 1, nil}, {"12345", 0, 12345, nil}, {"012345", 0, 012345, nil}, {"0x12345", 0, 0x12345, nil}, {"0X12345", 0, 0x12345, nil}, - {"12345x", 0, 0, ErrSyntax}, - {"0xabcdefg123", 0, 0, ErrSyntax}, - {"123456789abc", 0, 0, ErrSyntax}, + {"12345x", 0, 0, strconv.ErrSyntax}, + {"0xabcdefg123", 0, 0, strconv.ErrSyntax}, + {"123456789abc", 0, 0, strconv.ErrSyntax}, {"98765432100", 0, 98765432100, nil}, {"18446744073709551615", 0, 1<<64 - 1, nil}, - {"18446744073709551616", 0, 1<<64 - 1, ErrRange}, - {"18446744073709551620", 0, 1<<64 - 1, ErrRange}, + {"18446744073709551616", 0, 1<<64 - 1, strconv.ErrRange}, + {"18446744073709551620", 0, 1<<64 - 1, strconv.ErrRange}, {"0xFFFFFFFFFFFFFFFF", 0, 1<<64 - 1, nil}, - {"0x10000000000000000", 0, 1<<64 - 1, ErrRange}, + {"0x10000000000000000", 0, 1<<64 - 1, strconv.ErrRange}, {"01777777777777777777777", 0, 1<<64 - 1, nil}, - {"01777777777777777777778", 0, 0, ErrSyntax}, - {"02000000000000000000000", 0, 1<<64 - 1, ErrRange}, + {"01777777777777777777778", 0, 0, strconv.ErrSyntax}, + {"02000000000000000000000", 0, 1<<64 - 1, strconv.ErrRange}, {"0200000000000000000000", 0, 1 << 61, nil}, - {"0b", 0, 0, ErrSyntax}, - {"0B", 0, 0, ErrSyntax}, + {"0b", 0, 0, strconv.ErrSyntax}, + {"0B", 0, 0, strconv.ErrSyntax}, {"0b101", 0, 5, nil}, {"0B101", 0, 5, nil}, - {"0o", 0, 0, ErrSyntax}, - {"0O", 0, 0, ErrSyntax}, + {"0o", 0, 0, strconv.ErrSyntax}, + {"0O", 0, 0, strconv.ErrSyntax}, {"0o377", 0, 255, nil}, {"0O377", 0, 255, nil}, // underscores allowed with base == 0 only {"1_2_3_4_5", 0, 12345, nil}, // base 0 => 10 - {"_12345", 0, 0, ErrSyntax}, - {"1__2345", 0, 0, ErrSyntax}, - {"12345_", 0, 0, ErrSyntax}, + {"_12345", 0, 0, strconv.ErrSyntax}, + {"1__2345", 0, 0, strconv.ErrSyntax}, + {"12345_", 0, 0, strconv.ErrSyntax}, - {"1_2_3_4_5", 10, 0, ErrSyntax}, // base 10 - {"_12345", 10, 0, ErrSyntax}, - {"1__2345", 10, 0, ErrSyntax}, - {"12345_", 10, 0, ErrSyntax}, + {"1_2_3_4_5", 10, 0, strconv.ErrSyntax}, // base 10 + {"_12345", 10, 0, strconv.ErrSyntax}, + {"1__2345", 10, 0, strconv.ErrSyntax}, + {"12345_", 10, 0, strconv.ErrSyntax}, {"0x_1_2_3_4_5", 0, 0x12345, nil}, // base 0 => 16 - {"_0x12345", 0, 0, ErrSyntax}, - {"0x__12345", 0, 0, ErrSyntax}, - {"0x1__2345", 0, 0, ErrSyntax}, - {"0x1234__5", 0, 0, ErrSyntax}, - {"0x12345_", 0, 0, ErrSyntax}, - - {"1_2_3_4_5", 16, 0, ErrSyntax}, // base 16 - {"_12345", 16, 0, ErrSyntax}, - {"1__2345", 16, 0, ErrSyntax}, - {"1234__5", 16, 0, ErrSyntax}, - {"12345_", 16, 0, ErrSyntax}, + {"_0x12345", 0, 0, strconv.ErrSyntax}, + {"0x__12345", 0, 0, strconv.ErrSyntax}, + {"0x1__2345", 0, 0, strconv.ErrSyntax}, + {"0x1234__5", 0, 0, strconv.ErrSyntax}, + {"0x12345_", 0, 0, strconv.ErrSyntax}, + + {"1_2_3_4_5", 16, 0, strconv.ErrSyntax}, // base 16 + {"_12345", 16, 0, strconv.ErrSyntax}, + {"1__2345", 16, 0, strconv.ErrSyntax}, + {"1234__5", 16, 0, strconv.ErrSyntax}, + {"12345_", 16, 0, strconv.ErrSyntax}, {"0_1_2_3_4_5", 0, 012345, nil}, // base 0 => 8 (0377) - {"_012345", 0, 0, ErrSyntax}, - {"0__12345", 0, 0, ErrSyntax}, - {"01234__5", 0, 0, ErrSyntax}, - {"012345_", 0, 0, ErrSyntax}, + {"_012345", 0, 0, strconv.ErrSyntax}, + {"0__12345", 0, 0, strconv.ErrSyntax}, + {"01234__5", 0, 0, strconv.ErrSyntax}, + {"012345_", 0, 0, strconv.ErrSyntax}, {"0o_1_2_3_4_5", 0, 012345, nil}, // base 0 => 8 (0o377) - {"_0o12345", 0, 0, ErrSyntax}, - {"0o__12345", 0, 0, ErrSyntax}, - {"0o1234__5", 0, 0, ErrSyntax}, - {"0o12345_", 0, 0, ErrSyntax}, + {"_0o12345", 0, 0, strconv.ErrSyntax}, + {"0o__12345", 0, 0, strconv.ErrSyntax}, + {"0o1234__5", 0, 0, strconv.ErrSyntax}, + {"0o12345_", 0, 0, strconv.ErrSyntax}, - {"0_1_2_3_4_5", 8, 0, ErrSyntax}, // base 8 - {"_012345", 8, 0, ErrSyntax}, - {"0__12345", 8, 0, ErrSyntax}, - {"01234__5", 8, 0, ErrSyntax}, - {"012345_", 8, 0, ErrSyntax}, + {"0_1_2_3_4_5", 8, 0, strconv.ErrSyntax}, // base 8 + {"_012345", 8, 0, strconv.ErrSyntax}, + {"0__12345", 8, 0, strconv.ErrSyntax}, + {"01234__5", 8, 0, strconv.ErrSyntax}, + {"012345_", 8, 0, strconv.ErrSyntax}, {"0b_1_0_1", 0, 5, nil}, // base 0 => 2 (0b101) - {"_0b101", 0, 0, ErrSyntax}, - {"0b__101", 0, 0, ErrSyntax}, - {"0b1__01", 0, 0, ErrSyntax}, - {"0b10__1", 0, 0, ErrSyntax}, - {"0b101_", 0, 0, ErrSyntax}, + {"_0b101", 0, 0, strconv.ErrSyntax}, + {"0b__101", 0, 0, strconv.ErrSyntax}, + {"0b1__01", 0, 0, strconv.ErrSyntax}, + {"0b10__1", 0, 0, strconv.ErrSyntax}, + {"0b101_", 0, 0, strconv.ErrSyntax}, - {"1_0_1", 2, 0, ErrSyntax}, // base 2 - {"_101", 2, 0, ErrSyntax}, - {"1_01", 2, 0, ErrSyntax}, - {"10_1", 2, 0, ErrSyntax}, - {"101_", 2, 0, ErrSyntax}, + {"1_0_1", 2, 0, strconv.ErrSyntax}, // base 2 + {"_101", 2, 0, strconv.ErrSyntax}, + {"1_01", 2, 0, strconv.ErrSyntax}, + {"10_1", 2, 0, strconv.ErrSyntax}, + {"101_", 2, 0, strconv.ErrSyntax}, } type parseInt64Test struct { @@ -138,7 +139,7 @@ type parseInt64Test struct { } var parseInt64Tests = []parseInt64Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"-0", 0, nil}, {"+0", 0, nil}, @@ -153,16 +154,16 @@ var parseInt64Tests = []parseInt64Test{ {"-98765432100", -98765432100, nil}, {"9223372036854775807", 1<<63 - 1, nil}, {"-9223372036854775807", -(1<<63 - 1), nil}, - {"9223372036854775808", 1<<63 - 1, ErrRange}, + {"9223372036854775808", 1<<63 - 1, strconv.ErrRange}, {"-9223372036854775808", -1 << 63, nil}, - {"9223372036854775809", 1<<63 - 1, ErrRange}, - {"-9223372036854775809", -1 << 63, ErrRange}, - {"-1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"-_12345", 0, ErrSyntax}, - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, - {"123%45", 0, ErrSyntax}, + {"9223372036854775809", 1<<63 - 1, strconv.ErrRange}, + {"-9223372036854775809", -1 << 63, strconv.ErrRange}, + {"-1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"-_12345", 0, strconv.ErrSyntax}, + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, + {"123%45", 0, strconv.ErrSyntax}, } type parseInt64BaseTest struct { @@ -173,7 +174,7 @@ type parseInt64BaseTest struct { } var parseInt64BaseTests = []parseInt64BaseTest{ - {"", 0, 0, ErrSyntax}, + {"", 0, 0, strconv.ErrSyntax}, {"0", 0, 0, nil}, {"-0", 0, 0, nil}, {"1", 0, 1, nil}, @@ -184,16 +185,16 @@ var parseInt64BaseTests = []parseInt64BaseTest{ {"-012345", 0, -012345, nil}, {"0x12345", 0, 0x12345, nil}, {"-0X12345", 0, -0x12345, nil}, - {"12345x", 0, 0, ErrSyntax}, - {"-12345x", 0, 0, ErrSyntax}, + {"12345x", 0, 0, strconv.ErrSyntax}, + {"-12345x", 0, 0, strconv.ErrSyntax}, {"98765432100", 0, 98765432100, nil}, {"-98765432100", 0, -98765432100, nil}, {"9223372036854775807", 0, 1<<63 - 1, nil}, {"-9223372036854775807", 0, -(1<<63 - 1), nil}, - {"9223372036854775808", 0, 1<<63 - 1, ErrRange}, + {"9223372036854775808", 0, 1<<63 - 1, strconv.ErrRange}, {"-9223372036854775808", 0, -1 << 63, nil}, - {"9223372036854775809", 0, 1<<63 - 1, ErrRange}, - {"-9223372036854775809", 0, -1 << 63, ErrRange}, + {"9223372036854775809", 0, 1<<63 - 1, strconv.ErrRange}, + {"-9223372036854775809", 0, -1 << 63, strconv.ErrRange}, // other bases {"g", 17, 16, nil}, @@ -207,9 +208,9 @@ var parseInt64BaseTests = []parseInt64BaseTest{ {"1010", 2, 10, nil}, {"1000000000000000", 2, 1 << 15, nil}, {"111111111111111111111111111111111111111111111111111111111111111", 2, 1<<63 - 1, nil}, - {"1000000000000000000000000000000000000000000000000000000000000000", 2, 1<<63 - 1, ErrRange}, + {"1000000000000000000000000000000000000000000000000000000000000000", 2, 1<<63 - 1, strconv.ErrRange}, {"-1000000000000000000000000000000000000000000000000000000000000000", 2, -1 << 63, nil}, - {"-1000000000000000000000000000000000000000000000000000000000000001", 2, -1 << 63, ErrRange}, + {"-1000000000000000000000000000000000000000000000000000000000000001", 2, -1 << 63, strconv.ErrRange}, // base 8 {"-10", 8, -8, nil}, @@ -224,27 +225,27 @@ var parseInt64BaseTests = []parseInt64BaseTest{ // underscores {"-0x_1_2_3_4_5", 0, -0x12345, nil}, {"0x_1_2_3_4_5", 0, 0x12345, nil}, - {"-_0x12345", 0, 0, ErrSyntax}, - {"_-0x12345", 0, 0, ErrSyntax}, - {"_0x12345", 0, 0, ErrSyntax}, - {"0x__12345", 0, 0, ErrSyntax}, - {"0x1__2345", 0, 0, ErrSyntax}, - {"0x1234__5", 0, 0, ErrSyntax}, - {"0x12345_", 0, 0, ErrSyntax}, + {"-_0x12345", 0, 0, strconv.ErrSyntax}, + {"_-0x12345", 0, 0, strconv.ErrSyntax}, + {"_0x12345", 0, 0, strconv.ErrSyntax}, + {"0x__12345", 0, 0, strconv.ErrSyntax}, + {"0x1__2345", 0, 0, strconv.ErrSyntax}, + {"0x1234__5", 0, 0, strconv.ErrSyntax}, + {"0x12345_", 0, 0, strconv.ErrSyntax}, {"-0_1_2_3_4_5", 0, -012345, nil}, // octal {"0_1_2_3_4_5", 0, 012345, nil}, // octal - {"-_012345", 0, 0, ErrSyntax}, - {"_-012345", 0, 0, ErrSyntax}, - {"_012345", 0, 0, ErrSyntax}, - {"0__12345", 0, 0, ErrSyntax}, - {"01234__5", 0, 0, ErrSyntax}, - {"012345_", 0, 0, ErrSyntax}, + {"-_012345", 0, 0, strconv.ErrSyntax}, + {"_-012345", 0, 0, strconv.ErrSyntax}, + {"_012345", 0, 0, strconv.ErrSyntax}, + {"0__12345", 0, 0, strconv.ErrSyntax}, + {"01234__5", 0, 0, strconv.ErrSyntax}, + {"012345_", 0, 0, strconv.ErrSyntax}, {"+0xf", 0, 0xf, nil}, {"-0xf", 0, -0xf, nil}, - {"0x+f", 0, 0, ErrSyntax}, - {"0x-f", 0, 0, ErrSyntax}, + {"0x+f", 0, 0, strconv.ErrSyntax}, + {"0x-f", 0, 0, strconv.ErrSyntax}, } type parseUint32Test struct { @@ -254,20 +255,20 @@ type parseUint32Test struct { } var parseUint32Tests = []parseUint32Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"1", 1, nil}, {"12345", 12345, nil}, {"012345", 12345, nil}, - {"12345x", 0, ErrSyntax}, + {"12345x", 0, strconv.ErrSyntax}, {"987654321", 987654321, nil}, {"4294967295", 1<<32 - 1, nil}, - {"4294967296", 1<<32 - 1, ErrRange}, - {"1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"_12345", 0, ErrSyntax}, - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, + {"4294967296", 1<<32 - 1, strconv.ErrRange}, + {"1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"_12345", 0, strconv.ErrSyntax}, + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, } type parseInt32Test struct { @@ -277,7 +278,7 @@ type parseInt32Test struct { } var parseInt32Tests = []parseInt32Test{ - {"", 0, ErrSyntax}, + {"", 0, strconv.ErrSyntax}, {"0", 0, nil}, {"-0", 0, nil}, {"1", 1, nil}, @@ -286,22 +287,22 @@ var parseInt32Tests = []parseInt32Test{ {"-12345", -12345, nil}, {"012345", 12345, nil}, {"-012345", -12345, nil}, - {"12345x", 0, ErrSyntax}, - {"-12345x", 0, ErrSyntax}, + {"12345x", 0, strconv.ErrSyntax}, + {"-12345x", 0, strconv.ErrSyntax}, {"987654321", 987654321, nil}, {"-987654321", -987654321, nil}, {"2147483647", 1<<31 - 1, nil}, {"-2147483647", -(1<<31 - 1), nil}, - {"2147483648", 1<<31 - 1, ErrRange}, + {"2147483648", 1<<31 - 1, strconv.ErrRange}, {"-2147483648", -1 << 31, nil}, - {"2147483649", 1<<31 - 1, ErrRange}, - {"-2147483649", -1 << 31, ErrRange}, - {"-1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed - {"-_12345", 0, ErrSyntax}, - {"_12345", 0, ErrSyntax}, - {"1__2345", 0, ErrSyntax}, - {"12345_", 0, ErrSyntax}, - {"123%45", 0, ErrSyntax}, + {"2147483649", 1<<31 - 1, strconv.ErrRange}, + {"-2147483649", -1 << 31, strconv.ErrRange}, + {"-1_2_3_4_5", 0, strconv.ErrSyntax}, // base=10 so no underscores allowed + {"-_12345", 0, strconv.ErrSyntax}, + {"_12345", 0, strconv.ErrSyntax}, + {"1__2345", 0, strconv.ErrSyntax}, + {"12345_", 0, strconv.ErrSyntax}, + {"123%45", 0, strconv.ErrSyntax}, } type numErrorTest struct { @@ -320,37 +321,37 @@ func init() { for i := range parseUint64Tests { test := &parseUint64Tests[i] if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} + test.err = &strconv.NumError{"ParseUint", test.in, test.err} } } for i := range parseUint64BaseTests { test := &parseUint64BaseTests[i] if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} + test.err = &strconv.NumError{"ParseUint", test.in, test.err} } } for i := range parseInt64Tests { test := &parseInt64Tests[i] if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} + test.err = &strconv.NumError{"ParseInt", test.in, test.err} } } for i := range parseInt64BaseTests { test := &parseInt64BaseTests[i] if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} + test.err = &strconv.NumError{"ParseInt", test.in, test.err} } } for i := range parseUint32Tests { test := &parseUint32Tests[i] if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} + test.err = &strconv.NumError{"ParseUint", test.in, test.err} } } for i := range parseInt32Tests { test := &parseInt32Tests[i] if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} + test.err = &strconv.NumError{"ParseInt", test.in, test.err} } } } @@ -358,7 +359,7 @@ func init() { func TestParseUint32(t *testing.T) { for i := range parseUint32Tests { test := &parseUint32Tests[i] - out, err := ParseUint(test.in, 10, 32) + out, err := strconv.ParseUint(test.in, 10, 32) if uint64(test.out) != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, 10, 32) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -369,7 +370,7 @@ func TestParseUint32(t *testing.T) { func TestParseUint64(t *testing.T) { for i := range parseUint64Tests { test := &parseUint64Tests[i] - out, err := ParseUint(test.in, 10, 64) + out, err := strconv.ParseUint(test.in, 10, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, 10, 64) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -380,7 +381,7 @@ func TestParseUint64(t *testing.T) { func TestParseUint64Base(t *testing.T) { for i := range parseUint64BaseTests { test := &parseUint64BaseTests[i] - out, err := ParseUint(test.in, test.base, 64) + out, err := strconv.ParseUint(test.in, test.base, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseUint(%q, %v, 64) = %v, %v want %v, %v", test.in, test.base, out, err, test.out, test.err) @@ -391,7 +392,7 @@ func TestParseUint64Base(t *testing.T) { func TestParseInt32(t *testing.T) { for i := range parseInt32Tests { test := &parseInt32Tests[i] - out, err := ParseInt(test.in, 10, 32) + out, err := strconv.ParseInt(test.in, 10, 32) if int64(test.out) != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, 10 ,32) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -402,7 +403,7 @@ func TestParseInt32(t *testing.T) { func TestParseInt64(t *testing.T) { for i := range parseInt64Tests { test := &parseInt64Tests[i] - out, err := ParseInt(test.in, 10, 64) + out, err := strconv.ParseInt(test.in, 10, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, 10, 64) = %v, %v want %v, %v", test.in, out, err, test.out, test.err) @@ -413,7 +414,7 @@ func TestParseInt64(t *testing.T) { func TestParseInt64Base(t *testing.T) { for i := range parseInt64BaseTests { test := &parseInt64BaseTests[i] - out, err := ParseInt(test.in, test.base, 64) + out, err := strconv.ParseInt(test.in, test.base, 64) if test.out != out || !errEqual(test.err, err) { t.Errorf("ParseInt(%q, %v, 64) = %v, %v want %v, %v", test.in, test.base, out, err, test.out, test.err) @@ -422,88 +423,48 @@ func TestParseInt64Base(t *testing.T) { } func TestParseUint(t *testing.T) { - switch IntSize { - case 32: - for i := range parseUint32Tests { - test := &parseUint32Tests[i] - out, err := ParseUint(test.in, 10, 0) - if uint64(test.out) != out || !errEqual(test.err, err) { - t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", - test.in, out, err, test.out, test.err) - } - } - case 64: - for i := range parseUint64Tests { - test := &parseUint64Tests[i] - out, err := ParseUint(test.in, 10, 0) - if test.out != out || !errEqual(test.err, err) { - t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", - test.in, out, err, test.out, test.err) - } + for i := range parseUint64Tests { + test := &parseUint64Tests[i] + out, err := strconv.ParseUint(test.in, 10, 0) + if test.out != out || !errEqual(test.err, err) { + t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) } } } func TestParseInt(t *testing.T) { - switch IntSize { - case 32: - for i := range parseInt32Tests { - test := &parseInt32Tests[i] - out, err := ParseInt(test.in, 10, 0) - if int64(test.out) != out || !errEqual(test.err, err) { - t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", - test.in, out, err, test.out, test.err) - } - } - case 64: - for i := range parseInt64Tests { - test := &parseInt64Tests[i] - out, err := ParseInt(test.in, 10, 0) - if test.out != out || !errEqual(test.err, err) { - t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", - test.in, out, err, test.out, test.err) - } + for i := range parseInt64Tests { + test := &parseInt64Tests[i] + out, err := strconv.ParseInt(test.in, 10, 0) + if test.out != out || !errEqual(test.err, err) { + t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) } } } func TestAtoi(t *testing.T) { - switch IntSize { - case 32: - for i := range parseInt32Tests { - test := &parseInt32Tests[i] - out, err := Atoi(test.in) - var testErr error - if test.err != nil { - testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err} - } - if int(test.out) != out || !errEqual(testErr, err) { - t.Errorf("Atoi(%q) = %v, %v want %v, %v", - test.in, out, err, test.out, testErr) - } + for i := range parseInt64Tests { + test := &parseInt64Tests[i] + out, err := strconv.Atoi(test.in) + var testErr error + if test.err != nil { + testErr = &strconv.NumError{"Atoi", test.in, test.err.(*strconv.NumError).Err} } - case 64: - for i := range parseInt64Tests { - test := &parseInt64Tests[i] - out, err := Atoi(test.in) - var testErr error - if test.err != nil { - testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err} - } - if test.out != int64(out) || !errEqual(testErr, err) { - t.Errorf("Atoi(%q) = %v, %v want %v, %v", - test.in, out, err, test.out, testErr) - } + if test.out != int64(out) || !errEqual(testErr, err) { + t.Errorf("Atoi(%q) = %v, %v want %v, %v", + test.in, out, err, test.out, testErr) } } } func bitSizeErrStub(name string, bitSize int) error { - return BitSizeError(name, "0", bitSize) + return strconv.BitSizeError(name, "0", bitSize) } func baseErrStub(name string, base int) error { - return BaseError(name, "0", base) + return strconv.BaseError(name, "0", base) } func noErrStub(name string, arg int) error { @@ -545,7 +506,7 @@ func TestParseIntBitSize(t *testing.T) { for i := range parseBitSizeTests { test := &parseBitSizeTests[i] testErr := test.errStub("ParseInt", test.arg) - _, err := ParseInt("0", 0, test.arg) + _, err := strconv.ParseInt("0", 0, test.arg) if !equalError(testErr, err) { t.Errorf("ParseInt(\"0\", 0, %v) = 0, %v want 0, %v", test.arg, err, testErr) @@ -557,7 +518,7 @@ func TestParseUintBitSize(t *testing.T) { for i := range parseBitSizeTests { test := &parseBitSizeTests[i] testErr := test.errStub("ParseUint", test.arg) - _, err := ParseUint("0", 0, test.arg) + _, err := strconv.ParseUint("0", 0, test.arg) if !equalError(testErr, err) { t.Errorf("ParseUint(\"0\", 0, %v) = 0, %v want 0, %v", test.arg, err, testErr) @@ -569,7 +530,7 @@ func TestParseIntBase(t *testing.T) { for i := range parseBaseTests { test := &parseBaseTests[i] testErr := test.errStub("ParseInt", test.arg) - _, err := ParseInt("0", test.arg, 0) + _, err := strconv.ParseInt("0", test.arg, 0) if !equalError(testErr, err) { t.Errorf("ParseInt(\"0\", %v, 0) = 0, %v want 0, %v", test.arg, err, testErr) @@ -581,7 +542,7 @@ func TestParseUintBase(t *testing.T) { for i := range parseBaseTests { test := &parseBaseTests[i] testErr := test.errStub("ParseUint", test.arg) - _, err := ParseUint("0", test.arg, 0) + _, err := strconv.ParseUint("0", test.arg, 0) if !equalError(testErr, err) { t.Errorf("ParseUint(\"0\", %v, 0) = 0, %v want 0, %v", test.arg, err, testErr) @@ -591,21 +552,21 @@ func TestParseUintBase(t *testing.T) { func TestNumError(t *testing.T) { for _, test := range numErrorTests { - err := &NumError{ + err := &strconv.NumError{ Func: "ParseFloat", Num: test.num, Err: errors.New("failed"), } if got := err.Error(); got != test.want { - t.Errorf(`(&NumError{"ParseFloat", %q, "failed"}).Error() = %v, want %v`, test.num, got, test.want) + t.Errorf(`(&strconv.NumError{"ParseFloat", %q, "failed"}).Error() = %v, want %v`, test.num, got, test.want) } } } /* XXX: add when we support reflection / error un/wrapping. func TestNumErrorUnwrap(t *testing.T) { - err := &NumError{Err: ErrSyntax} - if !errEqual(err, ErrSyntax) { + err := &strconv.NumError{Err: strconv.ErrSyntax} + if !errEqual(err, strconv.ErrSyntax) { t.Error("errors.Is failed, wanted success") } } @@ -637,7 +598,7 @@ func benchmarkParseInt(b *testing.B, neg int) { b.Run(cs.name, func(b *testing.B) { s := fmt.Sprintf("%d", cs.num*int64(neg)) for i := 0; i < b.N; i++ { - out, _ := ParseInt(s, 10, 64) + out, _ := strconv.ParseInt(s, 10, 64) BenchSink += int(out) } }) @@ -659,7 +620,7 @@ func benchmarkAtoi(b *testing.B, neg int) { {"26bit", 1<<26 - 1}, {"31bit", 1<<31 - 1}, } - if IntSize == 64 { + if strconv.IntSize == 64 { cases = append(cases, []benchCase{ {"56bit", 1<<56 - 1}, {"63bit", 1<<63 - 1}, @@ -669,7 +630,7 @@ func benchmarkAtoi(b *testing.B, neg int) { b.Run(cs.name, func(b *testing.B) { s := fmt.Sprintf("%d", cs.num*int64(neg)) for i := 0; i < b.N; i++ { - out, _ := Atoi(s) + out, _ := strconv.Atoi(s) BenchSink += out } }) diff --git a/gnovm/stdlibs/strconv/decimal_test.gno b/gnovm/stdlibs/strconv/decimal_test.gno index 9dc8c997b9c..58036fe69b0 100644 --- a/gnovm/stdlibs/strconv/decimal_test.gno +++ b/gnovm/stdlibs/strconv/decimal_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "testing" ) @@ -31,7 +32,7 @@ var shifttests = []shiftTest{ func TestDecimalShift(t *testing.T) { for i := 0; i < len(shifttests); i++ { test := &shifttests[i] - d := NewDecimal(test.i) + d := strconv.NewDecimal(test.i) d.Shift(test.shift) s := d.String() if s != test.out { @@ -69,21 +70,21 @@ var roundtests = []roundTest{ func TestDecimalRound(t *testing.T) { for i := 0; i < len(roundtests); i++ { test := &roundtests[i] - d := NewDecimal(test.i) + d := strconv.NewDecimal(test.i) d.RoundDown(test.nd) s := d.String() if s != test.down { t.Errorf("Decimal %v RoundDown %d = %v, want %v", test.i, test.nd, s, test.down) } - d = NewDecimal(test.i) + d = strconv.NewDecimal(test.i) d.Round(test.nd) s = d.String() if s != test.round { t.Errorf("Decimal %v Round %d = %v, want %v", test.i, test.nd, s, test.down) } - d = NewDecimal(test.i) + d = strconv.NewDecimal(test.i) d.RoundUp(test.nd) s = d.String() if s != test.up { @@ -115,7 +116,7 @@ var roundinttests = []roundIntTest{ func TestDecimalRoundedInteger(t *testing.T) { for i := 0; i < len(roundinttests); i++ { test := roundinttests[i] - d := NewDecimal(test.i) + d := strconv.NewDecimal(test.i) d.Shift(test.shift) num := d.RoundedInteger() if num != test.int { diff --git a/gnovm/stdlibs/strconv/ftoa_test.gno b/gnovm/stdlibs/strconv/ftoa_test.gno index df1cc733827..bb860d62278 100644 --- a/gnovm/stdlibs/strconv/ftoa_test.gno +++ b/gnovm/stdlibs/strconv/ftoa_test.gno @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "math" "math/rand" + "strconv" "testing" ) @@ -176,20 +177,20 @@ var ftoatests = []ftoaTest{ func TestFtoa(t *testing.T) { for i := 0; i < len(ftoatests); i++ { test := &ftoatests[i] - s := FormatFloat(test.f, test.fmt, test.prec, 64) + s := strconv.FormatFloat(test.f, test.fmt, test.prec, 64) if s != test.s { t.Error("testN=64", test.f, string(test.fmt), test.prec, "want", test.s, "got", s) } - x := AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 64) + x := strconv.AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 64) if string(x) != "abc"+test.s { t.Error("AppendFloat testN=64", test.f, string(test.fmt), test.prec, "want", "abc"+test.s, "got", string(x)) } if float64(float32(test.f)) == test.f && test.fmt != 'b' { - s := FormatFloat(test.f, test.fmt, test.prec, 32) + s := strconv.FormatFloat(test.f, test.fmt, test.prec, 32) if s != test.s { t.Error("testN=32", test.f, string(test.fmt), test.prec, "want", test.s, "got", s) } - x := AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 32) + x := strconv.AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 32) if string(x) != "abc"+test.s { t.Error("AppendFloat testN=32", test.f, string(test.fmt), test.prec, "want", "abc"+test.s, "got", string(x)) } @@ -201,15 +202,15 @@ func TestFtoaPowersOfTwo(t *testing.T) { for exp := -2048; exp <= 2048; exp++ { f := math.Ldexp(1, exp) if !math.IsInf(f, 0) { - s := FormatFloat(f, 'e', -1, 64) - if x, _ := ParseFloat(s, 64); x != f { + s := strconv.FormatFloat(f, 'e', -1, 64) + if x, _ := strconv.ParseFloat(s, 64); x != f { t.Errorf("failed roundtrip %v => %s => %v", f, s, x) } } f32 := float32(f) if !math.IsInf(float64(f32), 0) { - s := FormatFloat(float64(f32), 'e', -1, 32) - if x, _ := ParseFloat(s, 32); float32(x) != f32 { + s := strconv.FormatFloat(float64(f32), 'e', -1, 32) + if x, _ := strconv.ParseFloat(s, 32); float32(x) != f32 { t.Errorf("failed roundtrip %v => %s => %v", f32, s, float32(x)) } } @@ -226,19 +227,19 @@ func TestFtoaRandom(t *testing.T) { bits := uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) x := math.Float64frombits(bits) - shortFast := FormatFloat(x, 'g', -1, 64) - SetOptimize(false) - shortSlow := FormatFloat(x, 'g', -1, 64) - SetOptimize(true) + shortFast := strconv.FormatFloat(x, 'g', -1, 64) + strconv.SetOptimize(false) + shortSlow := strconv.FormatFloat(x, 'g', -1, 64) + strconv.SetOptimize(true) if shortSlow != shortFast { t.Errorf("%b printed as %s, want %s", x, shortFast, shortSlow) } prec := rand.IntN(12) + 5 - shortFast = FormatFloat(x, 'e', prec, 64) - SetOptimize(false) - shortSlow = FormatFloat(x, 'e', prec, 64) - SetOptimize(true) + shortFast = strconv.FormatFloat(x, 'e', prec, 64) + strconv.SetOptimize(false) + shortSlow = strconv.FormatFloat(x, 'e', prec, 64) + strconv.SetOptimize(true) if shortSlow != shortFast { t.Errorf("%b printed as %s, want %s", x, shortFast, shortSlow) } @@ -251,7 +252,7 @@ func TestFormatFloatInvalidBitSize(t *testing.T) { t.Fatalf("expected panic due to invalid bitSize") } }() - _ = FormatFloat(3.14, 'g', -1, 100) + _ = strconv.FormatFloat(3.14, 'g', -1, 100) } var ftoaBenches = []struct { @@ -305,7 +306,7 @@ func BenchmarkFormatFloat(b *testing.B) { for _, c := range ftoaBenches { b.Run(c.name, func(b *testing.B) { for i := 0; i < b.N; i++ { - FormatFloat(c.float, c.fmt, c.prec, c.bitSize) + strconv.FormatFloat(c.float, c.fmt, c.prec, c.bitSize) } }) } @@ -316,7 +317,7 @@ func BenchmarkAppendFloat(b *testing.B) { for _, c := range ftoaBenches { b.Run(c.name, func(b *testing.B) { for i := 0; i < b.N; i++ { - AppendFloat(dst[:0], c.float, c.fmt, c.prec, c.bitSize) + strconv.AppendFloat(dst[:0], c.float, c.fmt, c.prec, c.bitSize) } }) } diff --git a/gnovm/stdlibs/strconv/ftoaryu_test.gno b/gnovm/stdlibs/strconv/ftoaryu_test.gno index bd969e8e997..8a7cb1fb97d 100644 --- a/gnovm/stdlibs/strconv/ftoaryu_test.gno +++ b/gnovm/stdlibs/strconv/ftoaryu_test.gno @@ -2,16 +2,17 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( "math" + "strconv" "testing" ) func TestMulByLog2Log10(t *testing.T) { for x := -1600; x <= +1600; x++ { - iMath := MulByLog2Log10(x) + iMath := strconv.MulByLog2Log10(x) fMath := int(math.Floor(float64(x) * math.Ln2 / math.Ln10)) if iMath != fMath { t.Errorf("mulByLog2Log10(%d) failed: %d vs %d\n", x, iMath, fMath) @@ -21,7 +22,7 @@ func TestMulByLog2Log10(t *testing.T) { func TestMulByLog10Log2(t *testing.T) { for x := -500; x <= +500; x++ { - iMath := MulByLog10Log2(x) + iMath := strconv.MulByLog10Log2(x) fMath := int(math.Floor(float64(x) * math.Ln10 / math.Ln2)) if iMath != fMath { t.Errorf("mulByLog10Log2(%d) failed: %d vs %d\n", x, iMath, fMath) diff --git a/gnovm/stdlibs/strconv/itoa_test.gno b/gnovm/stdlibs/strconv/itoa_test.gno index b76acc78183..e463edeb5eb 100644 --- a/gnovm/stdlibs/strconv/itoa_test.gno +++ b/gnovm/stdlibs/strconv/itoa_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "testing" ) @@ -60,24 +61,24 @@ var itob64tests = []itob64Test{ func TestItoa(t *testing.T) { for _, test := range itob64tests { - s := FormatInt(test.in, test.base) + s := strconv.FormatInt(test.in, test.base) if s != test.out { t.Errorf("FormatInt(%v, %v) = %v want %v", test.in, test.base, s, test.out) } - x := AppendInt([]byte("abc"), test.in, test.base) + x := strconv.AppendInt([]byte("abc"), test.in, test.base) if string(x) != "abc"+test.out { t.Errorf("AppendInt(%q, %v, %v) = %q want %v", "abc", test.in, test.base, x, test.out) } if test.in >= 0 { - s := FormatUint(uint64(test.in), test.base) + s := strconv.FormatUint(uint64(test.in), test.base) if s != test.out { t.Errorf("FormatUint(%v, %v) = %v want %v", test.in, test.base, s, test.out) } - x := AppendUint(nil, uint64(test.in), test.base) + x := strconv.AppendUint(nil, uint64(test.in), test.base) if string(x) != test.out { t.Errorf("AppendUint(%q, %v, %v) = %q want %v", "abc", uint64(test.in), test.base, x, test.out) @@ -85,7 +86,7 @@ func TestItoa(t *testing.T) { } if test.base == 10 && int64(int(test.in)) == test.in { - s := Itoa(int(test.in)) + s := strconv.Itoa(int(test.in)) if s != test.out { t.Errorf("Itoa(%v) = %v want %v", test.in, s, test.out) @@ -99,7 +100,7 @@ func TestItoa(t *testing.T) { t.Fatalf("expected panic due to illegal base") } }() - FormatUint(12345678, 1) + strconv.FormatUint(12345678, 1) } type uitob64Test struct { @@ -119,12 +120,12 @@ var uitob64tests = []uitob64Test{ func TestUitoa(t *testing.T) { for _, test := range uitob64tests { - s := FormatUint(test.in, test.base) + s := strconv.FormatUint(test.in, test.base) if s != test.out { t.Errorf("FormatUint(%v, %v) = %v want %v", test.in, test.base, s, test.out) } - x := AppendUint([]byte("abc"), test.in, test.base) + x := strconv.AppendUint([]byte("abc"), test.in, test.base) if string(x) != "abc"+test.out { t.Errorf("AppendUint(%q, %v, %v) = %q want %v", "abc", test.in, test.base, x, test.out) @@ -161,7 +162,7 @@ var varlenUints = []struct { func TestFormatUintVarlen(t *testing.T) { for _, test := range varlenUints { - s := FormatUint(test.in, 10) + s := strconv.FormatUint(test.in, 10) if s != test.out { t.Errorf("FormatUint(%v, 10) = %v want %v", test.in, s, test.out) } @@ -171,7 +172,7 @@ func TestFormatUintVarlen(t *testing.T) { func BenchmarkFormatInt(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range itob64tests { - s := FormatInt(test.in, test.base) + s := strconv.FormatInt(test.in, test.base) BenchSink += len(s) } } @@ -181,7 +182,7 @@ func BenchmarkAppendInt(b *testing.B) { dst := make([]byte, 0, 30) for i := 0; i < b.N; i++ { for _, test := range itob64tests { - dst = AppendInt(dst[:0], test.in, test.base) + dst = strconv.AppendInt(dst[:0], test.in, test.base) BenchSink += len(dst) } } @@ -190,7 +191,7 @@ func BenchmarkAppendInt(b *testing.B) { func BenchmarkFormatUint(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range uitob64tests { - s := FormatUint(test.in, test.base) + s := strconv.FormatUint(test.in, test.base) BenchSink += len(s) } } @@ -200,7 +201,7 @@ func BenchmarkAppendUint(b *testing.B) { dst := make([]byte, 0, 30) for i := 0; i < b.N; i++ { for _, test := range uitob64tests { - dst = AppendUint(dst[:0], test.in, test.base) + dst = strconv.AppendUint(dst[:0], test.in, test.base) BenchSink += len(dst) } } @@ -209,9 +210,9 @@ func BenchmarkAppendUint(b *testing.B) { func BenchmarkFormatIntSmall(b *testing.B) { smallInts := []int64{7, 42} for _, smallInt := range smallInts { - b.Run(Itoa(int(smallInt)), func(b *testing.B) { + b.Run(strconv.Itoa(int(smallInt)), func(b *testing.B) { for i := 0; i < b.N; i++ { - s := FormatInt(smallInt, 10) + s := strconv.FormatInt(smallInt, 10) BenchSink += len(s) } }) @@ -222,7 +223,7 @@ func BenchmarkAppendIntSmall(b *testing.B) { dst := make([]byte, 0, 30) const smallInt = 42 for i := 0; i < b.N; i++ { - dst = AppendInt(dst[:0], smallInt, 10) + dst = strconv.AppendInt(dst[:0], smallInt, 10) BenchSink += len(dst) } } @@ -232,7 +233,7 @@ func BenchmarkAppendUintVarlen(b *testing.B) { b.Run(test.out, func(b *testing.B) { dst := make([]byte, 0, 30) for j := 0; j < b.N; j++ { - dst = AppendUint(dst[:0], test.in, 10) + dst = strconv.AppendUint(dst[:0], test.in, 10) BenchSink += len(dst) } }) diff --git a/gnovm/stdlibs/strconv/quote_test.gno b/gnovm/stdlibs/strconv/quote_test.gno index b11e95461b0..40601365a30 100644 --- a/gnovm/stdlibs/strconv/quote_test.gno +++ b/gnovm/stdlibs/strconv/quote_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "strings" "testing" "unicode" @@ -14,8 +15,8 @@ import ( func TestIsPrint(t *testing.T) { n := 0 for r := rune(0); r <= unicode.MaxRune; r++ { - if IsPrint(r) != unicode.IsPrint(r) { - t.Errorf("IsPrint(%U)=%t incorrect", r, IsPrint(r)) + if strconv.IsPrint(r) != unicode.IsPrint(r) { + t.Errorf("IsPrint(%U)=%t incorrect", r, strconv.IsPrint(r)) n++ if n > 10 { return @@ -28,8 +29,8 @@ func TestIsPrint(t *testing.T) { func TestIsGraphic(t *testing.T) { n := 0 for r := rune(0); r <= unicode.MaxRune; r++ { - if IsGraphic(r) != unicode.IsGraphic(r) { - t.Errorf("IsGraphic(%U)=%t incorrect", r, IsGraphic(r)) + if strconv.IsGraphic(r) != unicode.IsGraphic(r) { + t.Errorf("IsGraphic(%U)=%t incorrect", r, strconv.IsGraphic(r)) n++ if n > 10 { return @@ -59,10 +60,10 @@ var quotetests = []quoteTest{ func TestQuote(t *testing.T) { for _, tt := range quotetests { - if out := Quote(tt.in); out != tt.out { + if out := strconv.Quote(tt.in); out != tt.out { t.Errorf("Quote(%s) = %s, want %s", tt.in, out, tt.out) } - if out := AppendQuote([]byte("abc"), tt.in); string(out) != "abc"+tt.out { + if out := strconv.AppendQuote([]byte("abc"), tt.in); string(out) != "abc"+tt.out { t.Errorf("AppendQuote(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.out) } } @@ -70,10 +71,10 @@ func TestQuote(t *testing.T) { func TestQuoteToASCII(t *testing.T) { for _, tt := range quotetests { - if out := QuoteToASCII(tt.in); out != tt.ascii { + if out := strconv.QuoteToASCII(tt.in); out != tt.ascii { t.Errorf("QuoteToASCII(%s) = %s, want %s", tt.in, out, tt.ascii) } - if out := AppendQuoteToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { + if out := strconv.AppendQuoteToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { t.Errorf("AppendQuoteToASCII(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.ascii) } } @@ -81,10 +82,10 @@ func TestQuoteToASCII(t *testing.T) { func TestQuoteToGraphic(t *testing.T) { for _, tt := range quotetests { - if out := QuoteToGraphic(tt.in); out != tt.graphic { + if out := strconv.QuoteToGraphic(tt.in); out != tt.graphic { t.Errorf("QuoteToGraphic(%s) = %s, want %s", tt.in, out, tt.graphic) } - if out := AppendQuoteToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { + if out := strconv.AppendQuoteToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { t.Errorf("AppendQuoteToGraphic(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.graphic) } } @@ -92,13 +93,13 @@ func TestQuoteToGraphic(t *testing.T) { func BenchmarkQuote(b *testing.B) { for i := 0; i < b.N; i++ { - Quote("\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") + strconv.Quote("\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") } } func BenchmarkQuoteRune(b *testing.B) { for i := 0; i < b.N; i++ { - QuoteRune('\a') + strconv.QuoteRune('\a') } } @@ -106,7 +107,7 @@ var benchQuoteBuf []byte func BenchmarkAppendQuote(b *testing.B) { for i := 0; i < b.N; i++ { - benchQuoteBuf = AppendQuote(benchQuoteBuf[:0], "\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") + benchQuoteBuf = strconv.AppendQuote(benchQuoteBuf[:0], "\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") } } @@ -114,7 +115,7 @@ var benchQuoteRuneBuf []byte func BenchmarkAppendQuoteRune(b *testing.B) { for i := 0; i < b.N; i++ { - benchQuoteRuneBuf = AppendQuoteRune(benchQuoteRuneBuf[:0], '\a') + benchQuoteRuneBuf = strconv.AppendQuoteRune(benchQuoteRuneBuf[:0], '\a') } } @@ -144,10 +145,10 @@ var quoterunetests = []quoteRuneTest{ func TestQuoteRune(t *testing.T) { for _, tt := range quoterunetests { - if out := QuoteRune(tt.in); out != tt.out { + if out := strconv.QuoteRune(tt.in); out != tt.out { t.Errorf("QuoteRune(%U) = %s, want %s", tt.in, out, tt.out) } - if out := AppendQuoteRune([]byte("abc"), tt.in); string(out) != "abc"+tt.out { + if out := strconv.AppendQuoteRune([]byte("abc"), tt.in); string(out) != "abc"+tt.out { t.Errorf("AppendQuoteRune(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.out) } } @@ -155,10 +156,10 @@ func TestQuoteRune(t *testing.T) { func TestQuoteRuneToASCII(t *testing.T) { for _, tt := range quoterunetests { - if out := QuoteRuneToASCII(tt.in); out != tt.ascii { + if out := strconv.QuoteRuneToASCII(tt.in); out != tt.ascii { t.Errorf("QuoteRuneToASCII(%U) = %s, want %s", tt.in, out, tt.ascii) } - if out := AppendQuoteRuneToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { + if out := strconv.AppendQuoteRuneToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { t.Errorf("AppendQuoteRuneToASCII(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.ascii) } } @@ -166,10 +167,10 @@ func TestQuoteRuneToASCII(t *testing.T) { func TestQuoteRuneToGraphic(t *testing.T) { for _, tt := range quoterunetests { - if out := QuoteRuneToGraphic(tt.in); out != tt.graphic { + if out := strconv.QuoteRuneToGraphic(tt.in); out != tt.graphic { t.Errorf("QuoteRuneToGraphic(%U) = %s, want %s", tt.in, out, tt.graphic) } - if out := AppendQuoteRuneToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { + if out := strconv.AppendQuoteRuneToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { t.Errorf("AppendQuoteRuneToGraphic(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.graphic) } } @@ -228,7 +229,7 @@ var canbackquotetests = []canBackquoteTest{ func TestCanBackquote(t *testing.T) { for _, tt := range canbackquotetests { - if out := CanBackquote(tt.in); out != tt.out { + if out := strconv.CanBackquote(tt.in); out != tt.out { t.Errorf("CanBackquote(%q) = %v, want %v", tt.in, out, tt.out) } } @@ -318,7 +319,7 @@ func TestUnquote(t *testing.T) { testUnquote(t, tt.out, tt.in, nil) } for _, s := range misquoted { - testUnquote(t, s, "", ErrSyntax) + testUnquote(t, s, "", strconv.ErrSyntax) } } @@ -332,7 +333,7 @@ func TestUnquoteInvalidUTF8(t *testing.T) { wantErr error }{ {in: `"foo"`, want: "foo"}, - {in: `"foo`, wantErr: ErrSyntax}, + {in: `"foo`, wantErr: strconv.ErrSyntax}, {in: `"` + "\xc0" + `"`, want: "\xef\xbf\xbd"}, {in: `"a` + "\xc0" + `"`, want: "a\xef\xbf\xbd"}, {in: `"\t` + "\xc0" + `"`, want: "\t\xef\xbf\xbd"}, @@ -344,7 +345,7 @@ func TestUnquoteInvalidUTF8(t *testing.T) { func testUnquote(t *testing.T, in, want string, wantErr error) { // Test Unquote. - got, gotErr := Unquote(in) + got, gotErr := strconv.Unquote(in) if got != want || gotErr != wantErr { t.Errorf("Unquote(%q) = (%q, %v), want (%q, %v)", in, got, gotErr, want, wantErr) } @@ -360,9 +361,9 @@ func testUnquote(t *testing.T, in, want string, wantErr error) { suffix = strings.ReplaceAll(suffix, in[:1], "") } in += suffix - got, gotErr = QuotedPrefix(in) + got, gotErr = strconv.QuotedPrefix(in) if gotErr == nil && wantErr != nil { - _, wantErr = Unquote(got) // original input had trailing junk, reparse with only valid prefix + _, wantErr = strconv.Unquote(got) // original input had trailing junk, reparse with only valid prefix want = got } if got != want || gotErr != wantErr { @@ -372,12 +373,12 @@ func testUnquote(t *testing.T, in, want string, wantErr error) { func BenchmarkUnquoteEasy(b *testing.B) { for i := 0; i < b.N; i++ { - Unquote(`"Give me a rock, paper and scissors and I will move the world."`) + strconv.Unquote(`"Give me a rock, paper and scissors and I will move the world."`) } } func BenchmarkUnquoteHard(b *testing.B) { for i := 0; i < b.N; i++ { - Unquote(`"\x47ive me a \x72ock, \x70aper and \x73cissors and \x49 will move the world."`) + strconv.Unquote(`"\x47ive me a \x72ock, \x70aper and \x73cissors and \x49 will move the world."`) } } diff --git a/gnovm/stdlibs/strconv/strconv_test.gno b/gnovm/stdlibs/strconv/strconv_test.gno index 5421ae84a93..e80eca207f7 100644 --- a/gnovm/stdlibs/strconv/strconv_test.gno +++ b/gnovm/stdlibs/strconv/strconv_test.gno @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package strconv +package strconv_test import ( + "strconv" "strings" "testing" ) @@ -21,24 +22,24 @@ var ( }{ {0, `AppendInt(localBuf[:0], 123, 10)`, func() { var localBuf [64]byte - AppendInt(localBuf[:0], 123, 10) + strconv.AppendInt(localBuf[:0], 123, 10) }}, - {0, `AppendInt(globalBuf[:0], 123, 10)`, func() { AppendInt(globalBuf[:0], 123, 10) }}, + {0, `AppendInt(globalBuf[:0], 123, 10)`, func() { strconv.AppendInt(globalBuf[:0], 123, 10) }}, {0, `AppendFloat(localBuf[:0], 1.23, 'g', 5, 64)`, func() { var localBuf [64]byte - AppendFloat(localBuf[:0], 1.23, 'g', 5, 64) + strconv.AppendFloat(localBuf[:0], 1.23, 'g', 5, 64) }}, - {0, `AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64)`, func() { AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64) }}, + {0, `AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64)`, func() { strconv.AppendFloat(globalBuf[:0], 1.23, 'g', 5, 64) }}, // In practice we see 7 for the next one, but allow some slop. // Before pre-allocation in appendQuotedWith, we saw 39. - {10, `AppendQuoteToASCII(nil, oneMB)`, func() { AppendQuoteToASCII(nil, string(oneMB)) }}, - {0, `ParseFloat("123.45", 64)`, func() { ParseFloat("123.45", 64) }}, - {0, `ParseFloat("123.456789123456789", 64)`, func() { ParseFloat("123.456789123456789", 64) }}, + {10, `AppendQuoteToASCII(nil, oneMB)`, func() { strconv.AppendQuoteToASCII(nil, string(oneMB)) }}, + {0, `ParseFloat("123.45", 64)`, func() { strconv.ParseFloat("123.45", 64) }}, + {0, `ParseFloat("123.456789123456789", 64)`, func() { strconv.ParseFloat("123.456789123456789", 64) }}, {0, `ParseFloat("1.000000000000000111022302462515654042363166809082031251", 64)`, func() { - ParseFloat("1.000000000000000111022302462515654042363166809082031251", 64) + strconv.ParseFloat("1.000000000000000111022302462515654042363166809082031251", 64) }}, {0, `ParseFloat("1.0000000000000001110223024625156540423631668090820312500...001", 64)`, func() { - ParseFloat(nextToOne, 64) + strconv.ParseFloat(nextToOne, 64) }}, } ) @@ -125,11 +126,11 @@ func TestAllocationsFromBytes(t *testing.T) { */ func TestErrorPrefixes(t *testing.T) { - _, errInt := Atoi("INVALID") - _, errBool := ParseBool("INVALID") - _, errFloat := ParseFloat("INVALID", 64) - _, errInt64 := ParseInt("INVALID", 10, 64) - _, errUint64 := ParseUint("INVALID", 10, 64) + _, errInt := strconv.Atoi("INVALID") + _, errBool := strconv.ParseBool("INVALID") + _, errFloat := strconv.ParseFloat("INVALID", 64) + _, errInt64 := strconv.ParseInt("INVALID", 10, 64) + _, errUint64 := strconv.ParseUint("INVALID", 10, 64) vectors := []struct { err error // Input error @@ -143,7 +144,7 @@ func TestErrorPrefixes(t *testing.T) { } for _, v := range vectors { - nerr, ok := v.err.(*NumError) + nerr, ok := v.err.(*strconv.NumError) if !ok { t.Errorf("test %s, error was not a *NumError", v.want) continue diff --git a/gnovm/stdlibs/strings/export_test.gno b/gnovm/stdlibs/strings/export_test.gno index c9646b36515..f4d48335424 100644 --- a/gnovm/stdlibs/strings/export_test.gno +++ b/gnovm/stdlibs/strings/export_test.gno @@ -5,7 +5,7 @@ package strings /* -func (r *Replacer) Replacer() interface{} { +func (r *Replacer) Replacer() any { r.once.Do(r.buildOnce) return r.r } diff --git a/gnovm/stdlibs/strings/printtrie_impl_test.gno b/gnovm/stdlibs/strings/printtrie_impl_test.gno new file mode 100644 index 00000000000..2a16c012eeb --- /dev/null +++ b/gnovm/stdlibs/strings/printtrie_impl_test.gno @@ -0,0 +1,29 @@ +package strings + +func (r *Replacer) PrintTrie() string { + r.buildOnce() + gen := r.r.(*genericReplacer) + return gen.printNode(&gen.root, 0) +} + +func (r *genericReplacer) printNode(t *trieNode, depth int) (s string) { + if t.priority > 0 { + s += "+" + } else { + s += "-" + } + s += "\n" + + if t.prefix != "" { + s += Repeat(".", depth) + t.prefix + s += r.printNode(t.next, depth+len(t.prefix)) + } else if t.table != nil { + for b, m := range r.mapping { + if int(m) != r.tableSize && t.table[m] != nil { + s += Repeat(".", depth) + string([]byte{byte(b)}) + s += r.printNode(t.table[m], depth+1) + } + } + } + return +} diff --git a/gnovm/stdlibs/strings/printtrie_test.gno b/gnovm/stdlibs/strings/printtrie_test.gno index b5b387b9bca..a51208f4756 100644 --- a/gnovm/stdlibs/strings/printtrie_test.gno +++ b/gnovm/stdlibs/strings/printtrie_test.gno @@ -1,37 +1,10 @@ -package strings +package strings_test import ( + "strings" "testing" ) -func (r *Replacer) PrintTrie() string { - r.buildOnce() - gen := r.r.(*genericReplacer) - return gen.printNode(&gen.root, 0) -} - -func (r *genericReplacer) printNode(t *trieNode, depth int) (s string) { - if t.priority > 0 { - s += "+" - } else { - s += "-" - } - s += "\n" - - if t.prefix != "" { - s += Repeat(".", depth) + t.prefix - s += r.printNode(t.next, depth+len(t.prefix)) - } else if t.table != nil { - for b, m := range r.mapping { - if int(m) != r.tableSize && t.table[m] != nil { - s += Repeat(".", depth) + string([]byte{byte(b)}) - s += r.printNode(t.table[m], depth+1) - } - } - } - return -} - func TestGenericTrieBuilding(t *testing.T) { testCases := []struct{ in, out string }{ {"abc;abdef;abdefgh;xx;xy;z", `- @@ -79,13 +52,13 @@ func TestGenericTrieBuilding(t *testing.T) { } for _, tc := range testCases { - keys := Split(tc.in, ";") + keys := strings.Split(tc.in, ";") args := make([]string, len(keys)*2) for i, key := range keys { args[i*2] = key } - got := NewReplacer(args...).PrintTrie() + got := strings.NewReplacer(args...).PrintTrie() // Remove tabs from tc.out wantbuf := make([]byte, 0, len(tc.out)) for i := 0; i < len(tc.out); i++ { diff --git a/gnovm/stdlibs/strings/reader_test.gno b/gnovm/stdlibs/strings/reader_test.gno index e357394ad90..a8047311550 100644 --- a/gnovm/stdlibs/strings/reader_test.gno +++ b/gnovm/stdlibs/strings/reader_test.gno @@ -74,7 +74,7 @@ func TestReaderAt(t *testing.T) { off int64 n int want string - wanterr interface{} + wanterr any }{ {0, 10, "0123456789", nil}, {1, 10, "123456789", io.EOF}, diff --git a/gnovm/stdlibs/sys/params/params.gno b/gnovm/stdlibs/sys/params/params.gno new file mode 100644 index 00000000000..66148bcd80b --- /dev/null +++ b/gnovm/stdlibs/sys/params/params.gno @@ -0,0 +1,39 @@ +package params + +// SetSysParam*(module, submodule, name, value) is used for directly setting params in +// ExecContext.Params, or SysParam. +// Access to "sys/params" must be restricted. + +func SetSysParamString(module, submodule, name string, val string) { + setSysParamString(module, submodule, name, val) +} + +func SetSysParamBool(module, submodule, name string, val bool) { + setSysParamBool(module, submodule, name, val) +} + +func SetSysParamInt64(module, submodule, name string, val int64) { + setSysParamInt64(module, submodule, name, val) +} + +func SetSysParamUint64(module, submodule, name string, val uint64) { + setSysParamUint64(module, submodule, name, val) +} + +func SetSysParamBytes(module, submodule, name string, val []byte) { + setSysParamBytes(module, submodule, name, val) +} + +func SetSysParamStrings(module, submodule, name string, val []string) { + if val == nil { + val = []string{} + } + setSysParamStrings(module, submodule, name, val) +} + +func setSysParamString(module, submodule, name string, val string) +func setSysParamBool(module, submodule, name string, val bool) +func setSysParamInt64(module, submodule, name string, val int64) +func setSysParamUint64(module, submodule, name string, val uint64) +func setSysParamBytes(module, submodule, name string, val []byte) +func setSysParamStrings(module, submodule, name string, val []string) diff --git a/gnovm/stdlibs/sys/params/params.go b/gnovm/stdlibs/sys/params/params.go new file mode 100644 index 00000000000..5b642a33307 --- /dev/null +++ b/gnovm/stdlibs/sys/params/params.go @@ -0,0 +1,69 @@ +package params + +import ( + "strings" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs/std" +) + +func X_setSysParamString(m *gno.Machine, module, submodule, name, val string) { + assertSysParamsRealm(m) + pk := prmkey(module, submodule, name) + std.GetContext(m).Params.SetString(pk, val) +} + +func X_setSysParamBool(m *gno.Machine, module, submodule, name string, val bool) { + assertSysParamsRealm(m) + pk := prmkey(module, submodule, name) + std.GetContext(m).Params.SetBool(pk, val) +} + +func X_setSysParamInt64(m *gno.Machine, module, submodule, name string, val int64) { + assertSysParamsRealm(m) + pk := prmkey(module, submodule, name) + std.GetContext(m).Params.SetInt64(pk, val) +} + +func X_setSysParamUint64(m *gno.Machine, module, submodule, name string, val uint64) { + assertSysParamsRealm(m) + pk := prmkey(module, submodule, name) + std.GetContext(m).Params.SetUint64(pk, val) +} + +func X_setSysParamBytes(m *gno.Machine, module, submodule, name string, val []byte) { + assertSysParamsRealm(m) + pk := prmkey(module, submodule, name) + std.GetContext(m).Params.SetBytes(pk, val) +} + +func X_setSysParamStrings(m *gno.Machine, module, submodule, name string, val []string) { + assertSysParamsRealm(m) + pk := prmkey(module, submodule, name) + std.GetContext(m).Params.SetStrings(pk, val) +} + +func assertSysParamsRealm(m *gno.Machine) { + // XXX improve + if len(m.Frames) < 2 { + panic("should not happen") + } + if m.Frames[len(m.Frames)-1].LastPackage.PkgPath != "sys/params" { + panic("should not happen") + } + if m.Frames[len(m.Frames)-2].LastPackage.PkgPath != "gno.land/r/sys/params" { + // XXX this should not happen after import rule. + panic(`"sys/params" can only be used from "gno.land/r/sys/params"`) + } +} + +func prmkey(module, submodule, name string) string { + // XXX consolidate validation + if strings.Contains(name, ":") { + panic("invalid param name: " + name) + } + if submodule == "" { + panic("submodule cannot be empty") + } + return module + ":" + submodule + ":" + name +} diff --git a/gnovm/stdlibs/testing/fuzz.gno b/gnovm/stdlibs/testing/fuzz.gno index 74d64c231b6..3eadf1182ac 100644 --- a/gnovm/stdlibs/testing/fuzz.gno +++ b/gnovm/stdlibs/testing/fuzz.gno @@ -182,14 +182,14 @@ type F struct { } // Runner is a type for the target function to fuzz. -type Runner func(*T, ...interface{}) +type Runner func(*T, ...any) // Fuzz applies the fuzzing process to the target function. func (f *F) Fuzz(run Runner, iter int) { f.evolve(iter) for _, input := range f.corpus { - args := make([]interface{}, len(f.corpus)) + args := make([]any, len(f.corpus)) for i := range args { args[i] = input } @@ -199,7 +199,7 @@ func (f *F) Fuzz(run Runner, iter int) { } // Add adds test values to initialize the corpus. -func (f *F) Add(values ...interface{}) []Fuzzer { +func (f *F) Add(values ...any) []Fuzzer { fuzzers := make([]Fuzzer, len(values)) for i, v := range values { @@ -272,7 +272,7 @@ func (f *F) Fail() { // Fatal is equivalent to Log followed by FailNow. // It logs the message and marks the fuzzing as failed. -func (f *F) Fatal(args ...interface{}) { +func (f *F) Fatal(args ...any) { var sb strings.Builder for _, arg := range args { diff --git a/gnovm/stdlibs/testing/fuzz_test.gno b/gnovm/stdlibs/testing/fuzz_test.gno index 307148c09b3..1e7bfc89a75 100644 --- a/gnovm/stdlibs/testing/fuzz_test.gno +++ b/gnovm/stdlibs/testing/fuzz_test.gno @@ -110,7 +110,7 @@ func Test_StringManipulation(t *T) { func TestFuzz(t *T) { f := F{} f.Add("hello", "world", "foo") - f.Fuzz(func(t *T, inputs ...interface{}) { + f.Fuzz(func(t *T, inputs ...any) { for _, input := range inputs { strInput, ok := input.(string) if !ok { diff --git a/gnovm/stdlibs/testing/match.gno b/gnovm/stdlibs/testing/match.gno index 8b099f37624..fae9391dc79 100644 --- a/gnovm/stdlibs/testing/match.gno +++ b/gnovm/stdlibs/testing/match.gno @@ -8,7 +8,6 @@ package testing import ( "fmt" - "regexp" "strings" "unicode" ) @@ -35,7 +34,7 @@ func (m simpleMatch) matches(name []string) (ok, partial bool) { if i >= len(m) { break } - if ok, _ := regexp.MatchString(m[i], s); !ok { + if ok, _ := matchString(m[i], s); !ok { return false, false } } @@ -48,7 +47,7 @@ func (m simpleMatch) verify(name string) error { } // Verify filters before doing any processing. for i, s := range m { - if _, err := regexp.MatchString(s, "non-empty"); err != nil { + if _, err := matchString(s, "non-empty"); err != nil { return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err) } } diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index fdafd9652ba..2551ca5cbc7 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -40,7 +40,7 @@ func Recover(result Setter) { } type Setter interface { - Set(v interface{}) + Set(v any) } func Short() bool { @@ -84,12 +84,12 @@ type testingFunc func(*T) // Not yet implemented: // func (t *T) Cleanup(f func()) { // func (t *T) Deadline() (deadline time.Time, ok bool) -func (t *T) Error(args ...interface{}) { +func (t *T) Error(args ...any) { t.Log(args...) t.Fail() } -func (t *T) Errorf(format string, args ...interface{}) { +func (t *T) Errorf(format string, args ...any) { t.Logf(format, args...) t.Fail() } @@ -129,21 +129,21 @@ func (t *T) printFailure() { } } -func (t *T) Fatal(args ...interface{}) { +func (t *T) Fatal(args ...any) { t.Log(args...) t.FailNow() } -func (t *T) Fatalf(format string, args ...interface{}) { +func (t *T) Fatalf(format string, args ...any) { t.Logf(format, args...) t.FailNow() } -func (t *T) Log(args ...interface{}) { +func (t *T) Log(args ...any) { t.log(fmt.Sprintln(args...)) } -func (t *T) Logf(format string, args ...interface{}) { +func (t *T) Logf(format string, args ...any) { t.log(fmt.Sprintf(format, args...)) t.log(fmt.Sprintln()) } @@ -176,7 +176,7 @@ func (t *T) Setenv(key, value string) { panic("not yet implemented") } -func (t *T) Skip(args ...interface{}) { +func (t *T) Skip(args ...any) { t.Log(args...) t.SkipNow() } @@ -191,7 +191,7 @@ func (t *T) Skipped() bool { return t.skipped } -func (t *T) Skipf(format string, args ...interface{}) { +func (t *T) Skipf(format string, args ...any) { t.Logf(format, args...) t.SkipNow() } @@ -233,33 +233,33 @@ type B struct { N int } -func (b *B) Cleanup(f func()) { panic("not yet implemented") } -func (b *B) Error(args ...interface{}) { panic("not yet implemented") } -func (b *B) Errorf(format string, args ...interface{}) { panic("not yet implemented") } -func (b *B) Fail() { panic("not yet implemented") } -func (b *B) FailNow() { panic("not yet implemented") } -func (b *B) Failed() bool { panic("not yet implemented") } -func (b *B) Fatal(args ...interface{}) { panic("not yet implemented") } -func (b *B) Fatalf(format string, args ...interface{}) { panic("not yet implemented") } -func (b *B) Helper() { panic("not yet implemented") } -func (b *B) Log(args ...interface{}) { panic("not yet implemented") } -func (b *B) Logf(format string, args ...interface{}) { panic("not yet implemented") } -func (b *B) Name() string { panic("not yet implemented") } -func (b *B) ReportAllocs() { panic("not yet implemented") } -func (b *B) ReportMetric(n float64, unit string) { panic("not yet implemented") } -func (b *B) ResetTimer() { panic("not yet implemented") } -func (b *B) Run(name string, f func(b *B)) bool { panic("not yet implemented") } -func (b *B) RunParallel(body func(*PB)) { panic("not yet implemented") } -func (b *B) SetBytes(n int64) { panic("not yet implemented") } -func (b *B) SetParallelism(p int) { panic("not yet implemented") } -func (b *B) Setenv(key, value string) { panic("not yet implemented") } -func (b *B) Skip(args ...interface{}) { panic("not yet implemented") } -func (b *B) SkipNow() { panic("not yet implemented") } -func (b *B) Skipf(format string, args ...interface{}) { panic("not yet implemented") } -func (b *B) Skipped() bool { panic("not yet implemented") } -func (b *B) StartTimer() { panic("not yet implemented") } -func (b *B) StopTimer() { panic("not yet implemented") } -func (b *B) TempDir() string { panic("not yet implemented") } +func (b *B) Cleanup(f func()) { panic("not yet implemented") } +func (b *B) Error(args ...any) { panic("not yet implemented") } +func (b *B) Errorf(format string, args ...any) { panic("not yet implemented") } +func (b *B) Fail() { panic("not yet implemented") } +func (b *B) FailNow() { panic("not yet implemented") } +func (b *B) Failed() bool { panic("not yet implemented") } +func (b *B) Fatal(args ...any) { panic("not yet implemented") } +func (b *B) Fatalf(format string, args ...any) { panic("not yet implemented") } +func (b *B) Helper() { panic("not yet implemented") } +func (b *B) Log(args ...any) { panic("not yet implemented") } +func (b *B) Logf(format string, args ...any) { panic("not yet implemented") } +func (b *B) Name() string { panic("not yet implemented") } +func (b *B) ReportAllocs() { panic("not yet implemented") } +func (b *B) ReportMetric(n float64, unit string) { panic("not yet implemented") } +func (b *B) ResetTimer() { panic("not yet implemented") } +func (b *B) Run(name string, f func(b *B)) bool { panic("not yet implemented") } +func (b *B) RunParallel(body func(*PB)) { panic("not yet implemented") } +func (b *B) SetBytes(n int64) { panic("not yet implemented") } +func (b *B) SetParallelism(p int) { panic("not yet implemented") } +func (b *B) Setenv(key, value string) { panic("not yet implemented") } +func (b *B) Skip(args ...any) { panic("not yet implemented") } +func (b *B) SkipNow() { panic("not yet implemented") } +func (b *B) Skipf(format string, args ...any) { panic("not yet implemented") } +func (b *B) Skipped() bool { panic("not yet implemented") } +func (b *B) StartTimer() { panic("not yet implemented") } +func (b *B) StopTimer() { panic("not yet implemented") } +func (b *B) TempDir() string { panic("not yet implemented") } // ---------------------------------------- // PB @@ -322,6 +322,12 @@ func formatDur(dur int64) string { // used to calculate execution times; only present in testing stdlibs func unixNano() int64 +// recovers panics and returns their related stacktraces, as well +func recoverWithStacktrace() (interface{}, string) + +// used to filter tests, we can't directly use regexp here due to a cyclic import; only present in testing stdlibs +func matchString(pat, str string) (result bool, err error) + func tRunner(t *T, fn testingFunc, verbose bool) { if !t.shouldRun(t.name) { return @@ -330,13 +336,13 @@ func tRunner(t *T, fn testingFunc, verbose bool) { start := unixNano() defer func() { - err := recover() + err, st := recoverWithStacktrace() switch err.(type) { case nil: case skipErr: default: t.Fail() - fmt.Fprintf(os.Stderr, "panic: %v\n", err) + fmt.Fprintf(os.Stderr, "panic: %v\nStacktrace:\n%s\n", err, st) } dur := unixNano() - start diff --git a/gnovm/stdlibs/testing/testing.go b/gnovm/stdlibs/testing/testing.go index 2c2e1d69904..325617de6a7 100644 --- a/gnovm/stdlibs/testing/testing.go +++ b/gnovm/stdlibs/testing/testing.go @@ -1,6 +1,20 @@ package testing +import ( + "errors" + + "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + func X_unixNano() int64 { // only implemented in testing stdlibs return 0 } + +func X_matchString(pat, str string) (result bool, err error) { + return false, errors.New("only implemented in testing stdlibs") +} + +func X_recoverWithStacktrace() (gnolang.TypedValue, string) { + panic("only available in testing stdlibs") +} diff --git a/gnovm/tests/backup/addr2.gno b/gnovm/tests/backup/addr2.gno index e3f55611d7d..4f8ac3953b8 100644 --- a/gnovm/tests/backup/addr2.gno +++ b/gnovm/tests/backup/addr2.gno @@ -10,7 +10,7 @@ type Email struct { Addr string } -func f(s string, r interface{}) error { +func f(s string, r any) error { return xml.Unmarshal([]byte(s), &r) } diff --git a/gnovm/tests/backup/assert0.gno b/gnovm/tests/backup/assert0.gno index 73921de6a33..09602b962af 100644 --- a/gnovm/tests/backup/assert0.gno +++ b/gnovm/tests/backup/assert0.gno @@ -32,7 +32,7 @@ func usesStringer(s MyStringer) { func main() { aType := reflect.TypeOf((*MyWriter)(nil)).Elem() - var t interface{} + var t any t = TestStruct{} var tw MyWriter var ok bool @@ -57,7 +57,7 @@ func main() { } _ = foo - var tt interface{} + var tt any tt = time.Nanosecond var myD MyStringer myD, ok = tt.(MyStringer) diff --git a/gnovm/tests/backup/assert1.gno b/gnovm/tests/backup/assert1.gno index 0754d7a8205..1399529e6f1 100644 --- a/gnovm/tests/backup/assert1.gno +++ b/gnovm/tests/backup/assert1.gno @@ -15,7 +15,7 @@ func (t TestStruct) String() string { func main() { aType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem() - var t interface{} + var t any t = time.Nanosecond s, ok := t.(fmt.Stringer) if !ok { @@ -36,7 +36,7 @@ func main() { } _ = foo - var tt interface{} + var tt any tt = TestStruct{} ss, ok := tt.(fmt.Stringer) if !ok { diff --git a/gnovm/tests/backup/complex4.gno b/gnovm/tests/backup/complex4.gno index 08ee1cfcfe9..9eead4ea002 100644 --- a/gnovm/tests/backup/complex4.gno +++ b/gnovm/tests/backup/complex4.gno @@ -2,7 +2,7 @@ package main import "fmt" -func f(a, b float64) interface{} { return complex(a, b) } +func f(a, b float64) any { return complex(a, b) } func main() { a := f(3, 2) diff --git a/gnovm/tests/backup/heap.gno b/gnovm/tests/backup/heap.gno index fc753325ea8..9322448225a 100644 --- a/gnovm/tests/backup/heap.gno +++ b/gnovm/tests/backup/heap.gno @@ -13,13 +13,13 @@ func (h IntHeap) Len() int { return len(h) } func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } -func (h *IntHeap) Push(x interface{}) { +func (h *IntHeap) Push(x any) { // Push and Pop use pointer receivers because they modify the slice's length, // not just its contents. *h = append(*h, x.(int)) } -func (h *IntHeap) Pop() interface{} { +func (h *IntHeap) Pop() any { old := *h n := len(old) x := old[n-1] diff --git a/gnovm/tests/backup/imag0.gno b/gnovm/tests/backup/imag0.gno index 9c950b35c33..53c9ae4780c 100644 --- a/gnovm/tests/backup/imag0.gno +++ b/gnovm/tests/backup/imag0.gno @@ -2,7 +2,7 @@ package main import "fmt" -func f(c complex128) interface{} { return imag(c) } +func f(c complex128) any { return imag(c) } func main() { c := complex(3, 2) diff --git a/gnovm/tests/backup/real0.gno b/gnovm/tests/backup/real0.gno index af3b8ff135a..81dd1e4ee22 100644 --- a/gnovm/tests/backup/real0.gno +++ b/gnovm/tests/backup/real0.gno @@ -2,7 +2,7 @@ package main import "fmt" -func f(c complex128) interface{} { return real(c) } +func f(c complex128) any { return real(c) } func main() { c := complex(3, 2) diff --git a/gnovm/tests/backup/select13.gno b/gnovm/tests/backup/select13.gno index 08ae13f68e6..7d0a5fe0af1 100644 --- a/gnovm/tests/backup/select13.gno +++ b/gnovm/tests/backup/select13.gno @@ -1,7 +1,7 @@ package main func main() { - var c interface{} = int64(1) + var c any = int64(1) q := make(chan struct{}) select { case q <- struct{}{}: diff --git a/gnovm/tests/backup/struct55.gno b/gnovm/tests/backup/struct55.gno index b2ca6bba62f..421c85be35b 100644 --- a/gnovm/tests/backup/struct55.gno +++ b/gnovm/tests/backup/struct55.gno @@ -9,7 +9,7 @@ type Logger struct { m []*log.Logger } -func (l *Logger) Infof(format string, args ...interface{}) { +func (l *Logger) Infof(format string, args ...any) { l.m[0].Printf(format, args...) } diff --git a/gnovm/tests/backup/type10.gno b/gnovm/tests/backup/type10.gno index 194feda5b52..6712fff3f76 100644 --- a/gnovm/tests/backup/type10.gno +++ b/gnovm/tests/backup/type10.gno @@ -11,7 +11,7 @@ var gzipWriterPools [10]*sync.Pool = [10]*sync.Pool{} func main() { level := 9 gzipWriterPools[level] = &sync.Pool{ - New: func() interface{} { + New: func() any { w, _ := gzip.NewWriterLevel(nil, level) return w }, diff --git a/gnovm/tests/backup/type23.gno b/gnovm/tests/backup/type23.gno index 1659e704798..7dac419900d 100644 --- a/gnovm/tests/backup/type23.gno +++ b/gnovm/tests/backup/type23.gno @@ -6,8 +6,8 @@ import ( ) func main() { - var v1 interface{} = 1 - var v2 interface{} + var v1 any = 1 + var v2 any var v3 http.ResponseWriter = httptest.NewRecorder() if r1, ok := v1.(string); ok { diff --git a/gnovm/tests/backup/type24.gno b/gnovm/tests/backup/type24.gno index 79449d66607..8905c4eb7da 100644 --- a/gnovm/tests/backup/type24.gno +++ b/gnovm/tests/backup/type24.gno @@ -18,7 +18,7 @@ func assertInt() { fmt.Println(r) }() - var v interface{} = 1 + var v any = 1 println(v.(string)) } @@ -28,7 +28,7 @@ func assertNil() { fmt.Println(r) }() - var v interface{} + var v any println(v.(string)) } diff --git a/gnovm/tests/backup/variadic7.gno b/gnovm/tests/backup/variadic7.gno index 7d20fd87f32..f5cc7633aa4 100644 --- a/gnovm/tests/backup/variadic7.gno +++ b/gnovm/tests/backup/variadic7.gno @@ -6,7 +6,7 @@ func main() { var a, b string pattern := "%s %s" - dest := []interface{}{&a, &b} + dest := []any{&a, &b} n, err := fmt.Sscanf("test1 test2", pattern, dest...) if err != nil || n != len(dest) { diff --git a/gnovm/tests/challenges/unused0.gno b/gnovm/tests/challenges/unused0.gno index d5b0ff75c17..dc9aa950dec 100644 --- a/gnovm/tests/challenges/unused0.gno +++ b/gnovm/tests/challenges/unused0.gno @@ -1,7 +1,7 @@ package main // NOTE: instead of patching the vm code, this should be handled by an -// integration of `gno transpile --gobuild`, which uses `go build` under +// integration of `gno tool transpile --gobuild`, which uses `go build` under // the hood and already detects unused variables. func main() { var x int diff --git a/gnovm/tests/files/add0.gno b/gnovm/tests/files/add0.gno index 32d26377599..391cd6e830f 100644 --- a/gnovm/tests/files/add0.gno +++ b/gnovm/tests/files/add0.gno @@ -1,7 +1,7 @@ package main func main() { - var a interface{} = 2 + 5 + var a any = 2 + 5 println(a.(int)) } diff --git a/gnovm/tests/files/add1.gno b/gnovm/tests/files/add1.gno index 650c089bc9a..408bd0f40ea 100644 --- a/gnovm/tests/files/add1.gno +++ b/gnovm/tests/files/add1.gno @@ -2,7 +2,7 @@ package main func main() { b := 2 - var a interface{} = 5 + b + var a any = 5 + b println(a.(int)) } diff --git a/gnovm/tests/files/add2.gno b/gnovm/tests/files/add2.gno index 7f37a784197..d5a510fb544 100644 --- a/gnovm/tests/files/add2.gno +++ b/gnovm/tests/files/add2.gno @@ -1,6 +1,6 @@ package main -type iface interface{} +type iface any func main() { b := 2 diff --git a/gnovm/tests/files/addr2b.gno b/gnovm/tests/files/addr2b.gno index 59a18904bea..4acb032f2db 100644 --- a/gnovm/tests/files/addr2b.gno +++ b/gnovm/tests/files/addr2b.gno @@ -10,7 +10,7 @@ type Email struct { Addr string } -func f(s string, r interface{}) interface{} { +func f(s string, r any) any { return json.Unmarshal([]byte(s), &r) } diff --git a/gnovm/tests/files/addressable_1.gno b/gnovm/tests/files/addressable_1.gno new file mode 100644 index 00000000000..30616145733 --- /dev/null +++ b/gnovm/tests/files/addressable_1.gno @@ -0,0 +1,38 @@ +package main + +func main() { + // Array pointers are addressable. + println(&getArrPtr1()[0]) + println(&getArrPtr2()[0]) + println(&getArrPtr3()[0]) + println(&new([1]int)[0]) + + // Array pointers are slicable. + println(getArrPtr1()[:]) + println(getArrPtr2()[:]) + println(getArrPtr3()[:]) + println(new([1]int)[:]) +} + +func getArrPtr1() *[1]int { + return &[1]int{1} +} + +func getArrPtr2() *[1]int { + a := [1]int{2} + return &a +} + +func getArrPtr3() *[1]int { + return new([1]int) +} + +// Output: +// &(1 int) +// &(2 int) +// &(0 int) +// &(0 int) +// slice[(1 int)] +// slice[(2 int)] +// slice[(0 int)] +// slice[(0 int)] diff --git a/gnovm/tests/files/addressable_10.gno b/gnovm/tests/files/addressable_10.gno new file mode 100644 index 00000000000..9052c172973 --- /dev/null +++ b/gnovm/tests/files/addressable_10.gno @@ -0,0 +1,12 @@ +package main + +type S struct { + i int +} + +func main() { + println(&new(S).i) +} + +// Output: +// &(0 int) diff --git a/gnovm/tests/files/addressable_10a_err.gno b/gnovm/tests/files/addressable_10a_err.gno new file mode 100644 index 00000000000..6781c6c67e5 --- /dev/null +++ b/gnovm/tests/files/addressable_10a_err.gno @@ -0,0 +1,14 @@ +package main + +func main() { + println(&getPtr()) +} + +type S struct{} + +func getPtr() *S { + return &S{} +} + +// Error: +// main/files/addressable_10a_err.gno:4:10: cannot take address of getPtr() diff --git a/gnovm/tests/files/addressable_10b_err.gno b/gnovm/tests/files/addressable_10b_err.gno new file mode 100644 index 00000000000..679f69b5285 --- /dev/null +++ b/gnovm/tests/files/addressable_10b_err.gno @@ -0,0 +1,12 @@ +package main + +type S struct { + i int +} + +func main() { + println(&new(S)) +} + +// Error: +// main/files/addressable_10b_err.gno:8:10: cannot take address of (const (new func(type{}) *main.S))(S) diff --git a/gnovm/tests/files/addressable_11.gno b/gnovm/tests/files/addressable_11.gno new file mode 100644 index 00000000000..f9ea1aa5f34 --- /dev/null +++ b/gnovm/tests/files/addressable_11.gno @@ -0,0 +1,39 @@ +package main + +func main() { + var ii **int + i := new(int) + ii = &i + println(&(*ii)) + println(&ii) + println(i) + println(ii) + println(&i) + + j := new(int) + println(&(*j)) + + println(&(*getPtr())) + + derefTypeAssert() +} + +func getPtr() *int { + return new(int) +} + +func derefTypeAssert() { + var i any + i = new(int) + println(&(*(i.(*int)))) +} + +// Output: +// &(&(0 int) *int) +// &(&(&(0 int) *int) **int) +// &(0 int) +// &(&(0 int) *int) +// &(&(0 int) *int) +// &(0 int) +// &(0 int) +// &(0 int) diff --git a/gnovm/tests/files/addressable_1a_err.gno b/gnovm/tests/files/addressable_1a_err.gno new file mode 100644 index 00000000000..a8c8c3b3c77 --- /dev/null +++ b/gnovm/tests/files/addressable_1a_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _ = &[1]int{1}[0] +} + +// Error: +// main/files/addressable_1a_err.gno:4:6: cannot take address of [(const (1 int))](const-type int){(const (1 int))}[(const (0 int))] diff --git a/gnovm/tests/files/addressable_1b_err.gno b/gnovm/tests/files/addressable_1b_err.gno new file mode 100644 index 00000000000..9d8a7d12e4b --- /dev/null +++ b/gnovm/tests/files/addressable_1b_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _ = [1]int{1}[:] +} + +// Error: +// main/files/addressable_1b_err.gno:4:6: cannot take address of [(const (1 int))](const-type int){(const (1 int))} diff --git a/gnovm/tests/files/addressable_1c_err.gno b/gnovm/tests/files/addressable_1c_err.gno new file mode 100644 index 00000000000..420531187df --- /dev/null +++ b/gnovm/tests/files/addressable_1c_err.gno @@ -0,0 +1,13 @@ +package main + +func main() { + _ = &getArr()[0] +} + +func getArr() [1]int { + arr := [1]int{1} + return arr +} + +// Error: +// main/files/addressable_1c_err.gno:4:6: cannot take address of getArr()[(const (0 int))] diff --git a/gnovm/tests/files/addressable_1d_err.gno b/gnovm/tests/files/addressable_1d_err.gno new file mode 100644 index 00000000000..56bb81c881f --- /dev/null +++ b/gnovm/tests/files/addressable_1d_err.gno @@ -0,0 +1,12 @@ +package main + +func main() { + _ = getArr()[:] +} + +func getArr() [1]int { + return [1]int{1} +} + +// Error: +// main/files/addressable_1d_err.gno:4:6: cannot take address of getArr() diff --git a/gnovm/tests/files/addressable_2.gno b/gnovm/tests/files/addressable_2.gno new file mode 100644 index 00000000000..1baf7f29e65 --- /dev/null +++ b/gnovm/tests/files/addressable_2.gno @@ -0,0 +1,40 @@ +package main + +func main() { + // Slices are addressable because the underlying array is addressable + // after slice initialization. + println(&[]int{1}[0]) + println(&getSlice1()[0]) + println(&getSlice2()[0]) + println(&[]int{1}) + + a := []int{1} + println(&append(a, 1, 2, 3, 4, 5)[5]) + + println([]int{1}[:]) + println(getSlice1()[:]) + println(getSlice2()[:]) + + b := []int{1} + println(append(b, 1, 2, 3, 4, 5)[:]) +} + +func getSlice1() []int { + return []int{2} +} + +func getSlice2() []int { + s := []int{3} + return s +} + +// Output: +// &(1 int) +// &(2 int) +// &(3 int) +// &(slice[(1 int)] []int) +// &(5 int) +// slice[(1 int)] +// slice[(2 int)] +// slice[(3 int)] +// slice[(1 int),(1 int),(2 int),(3 int),(4 int),(5 int)] diff --git a/gnovm/tests/files/addressable_2a_err.gno b/gnovm/tests/files/addressable_2a_err.gno new file mode 100644 index 00000000000..363fa3d2817 --- /dev/null +++ b/gnovm/tests/files/addressable_2a_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _ = &[]int{1}[:] +} + +// Error: +// main/files/addressable_2a_err.gno:4:6: cannot take address of [](const-type int){(const (1 int))}[:] diff --git a/gnovm/tests/files/addressable_2b_err.gno b/gnovm/tests/files/addressable_2b_err.gno new file mode 100644 index 00000000000..eef7fd37ca5 --- /dev/null +++ b/gnovm/tests/files/addressable_2b_err.gno @@ -0,0 +1,12 @@ +package main + +func main() { + _ = &getSlice() +} + +func getSlice() []int { + return []int{1} +} + +// Error: +// main/files/addressable_2b_err.gno:4:6: cannot take address of getSlice() diff --git a/gnovm/tests/files/addressable_3.gno b/gnovm/tests/files/addressable_3.gno new file mode 100644 index 00000000000..34e9a2e1edc --- /dev/null +++ b/gnovm/tests/files/addressable_3.gno @@ -0,0 +1,105 @@ +package main + +type S struct { + i int + ip *int + a [1]int + ap *[1]int + s []int +} + +func main() { + intPtr := new(int) + *intPtr = 9 + + v1 := S{ + i: 4, + ip: intPtr, + a: [1]int{5}, + ap: new([1]int), + s: []int{6}, + } + + // v1 and all members are addressable. + println(&v1) + println(&v1.i) + println(*v1.ip) + println(&v1.a[0]) + println(&v1.ap[0]) + println(&v1.s[0]) + println("") + + // Defining a struct as a pointer also makes a member addressable. + println(&(&S{i: 4}).i) + println("") + + // Print only the members that are addressable when S is not addressable. + println(*S{ip: intPtr}.ip) + println(&S{ap: new([1]int)}.ap[0]) + println(&S{s: []int{6}}.s[0]) + println("") + + // A struct value returned by a function is not addressable. + // Only certain members are addressable. + println(*getStruct().ip) + println(&getStruct().ap[0]) + println(&getStruct().s[0]) + println("") + + // A struct pointer value returned by a function has all members addressable. + println(&getStructPtr().i) + println(*getStructPtr().ip) + println(&getStructPtr().a[0]) + println(&getStructPtr().ap[0]) + println(&getStructPtr().s[0]) +} + +func getStruct() S { + intPtr := new(int) + *intPtr = 9 + + return S{ + i: 4, + ip: intPtr, + a: [1]int{5}, + ap: new([1]int), + s: []int{6}, + } +} + +func getStructPtr() *S { + intPtr := new(int) + *intPtr = 9 + + return &S{ + i: 4, + ip: intPtr, + a: [1]int{5}, + ap: new([1]int), + s: []int{6}, + } +} + +// Output: +// &(struct{(4 int),(&(9 int) *int),(array[(5 int)] [1]int),(&(array[(0 int)] [1]int) *[1]int),(slice[(6 int)] []int)} main.S) +// &(4 int) +// 9 +// &(5 int) +// &(0 int) +// &(6 int) +// +// &(4 int) +// +// 9 +// &(0 int) +// &(6 int) +// +// 9 +// &(0 int) +// &(6 int) +// +// &(4 int) +// 9 +// &(5 int) +// &(0 int) +// &(6 int) diff --git a/gnovm/tests/files/addressable_3a_err.gno b/gnovm/tests/files/addressable_3a_err.gno new file mode 100644 index 00000000000..dcf3883e79b --- /dev/null +++ b/gnovm/tests/files/addressable_3a_err.gno @@ -0,0 +1,14 @@ +package main + +type S struct { + i int +} + +func main() { + // Can't take the address of non-addressable member of + // a non-addressable struct. + _ = &S{i: 4}.i +} + +// Error: +// main/files/addressable_3a_err.gno:10:6: cannot take address of S{i: (const (4 int))}.i diff --git a/gnovm/tests/files/addressable_3b_err.gno b/gnovm/tests/files/addressable_3b_err.gno new file mode 100644 index 00000000000..db4e810155a --- /dev/null +++ b/gnovm/tests/files/addressable_3b_err.gno @@ -0,0 +1,16 @@ +package main + +type S struct { + i int +} + +func main() { + _ = &getStruct().i +} + +func getStruct() S { + return S{i: 9} +} + +// Error: +// main/files/addressable_3b_err.gno:8:6: cannot take address of getStruct().i diff --git a/gnovm/tests/files/addressable_3c_err.gno b/gnovm/tests/files/addressable_3c_err.gno new file mode 100644 index 00000000000..536b5f77475 --- /dev/null +++ b/gnovm/tests/files/addressable_3c_err.gno @@ -0,0 +1,17 @@ +package main + +type MyStruct struct { + Mp *int +} + +func makeT() MyStruct { + x := 10 + return MyStruct{Mp: &x} +} + +func main() { + _ = &makeT().Mp +} + +// Error: +// main/files/addressable_3c_err.gno:13:6: cannot take address of makeT().Mp diff --git a/gnovm/tests/files/addressable_3d_err.gno b/gnovm/tests/files/addressable_3d_err.gno new file mode 100644 index 00000000000..00d75f80402 --- /dev/null +++ b/gnovm/tests/files/addressable_3d_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _ = &(&struct{}{}) +} + +// Error: +// main/files/addressable_3d_err.gno:4:6: cannot take address of &(struct { }{}) diff --git a/gnovm/tests/files/addressable_4.gno b/gnovm/tests/files/addressable_4.gno new file mode 100644 index 00000000000..322cf8eabb3 --- /dev/null +++ b/gnovm/tests/files/addressable_4.gno @@ -0,0 +1,23 @@ +package main + +type S struct { + s string +} + +func main() { + // Strings are special; they are slicable even if not addressable. + println("hello"[:]) + println("hello"[0]) + println("hello"[2:4]) + println(S{s: "hello"}.s[:]) + println(S{s: "hello"}.s[0]) + println(S{s: "hello"}.s[2:4]) +} + +// Output: +// hello +// 104 +// ll +// hello +// 104 +// ll diff --git a/gnovm/tests/files/addressable_4a_err.gno b/gnovm/tests/files/addressable_4a_err.gno new file mode 100644 index 00000000000..17481976f3a --- /dev/null +++ b/gnovm/tests/files/addressable_4a_err.gno @@ -0,0 +1,9 @@ +package main + +func main() { + greeting := "hello" + _ = &greeting[2] +} + +// Error: +// main/files/addressable_4a_err.gno:5:6: cannot take address of greeting[(const (2 int))] diff --git a/gnovm/tests/files/addressable_5.gno b/gnovm/tests/files/addressable_5.gno new file mode 100644 index 00000000000..fa39ef42841 --- /dev/null +++ b/gnovm/tests/files/addressable_5.gno @@ -0,0 +1,14 @@ +package main + +import "encoding/binary" + +func main() { + // Verify that addressable results of expressions are + // still addressable when accessed via a selector. + var b []byte + le := &binary.LittleEndian + println(&le.AppendUint16(b, 0)[0]) +} + +// Output: +// &(0 uint8) diff --git a/gnovm/tests/files/addressable_5a_err_stdlibs.gno b/gnovm/tests/files/addressable_5a_err_stdlibs.gno new file mode 100644 index 00000000000..bc6318f511d --- /dev/null +++ b/gnovm/tests/files/addressable_5a_err_stdlibs.gno @@ -0,0 +1,11 @@ +package main + +import "math" + +func main() { + // Untyped constants are not addressable. + _ = &math.MaxUint8 +} + +// Error: +// main/files/addressable_5a_err_stdlibs.gno:7:6: cannot take address of (const (255 bigint)) diff --git a/gnovm/tests/files/addressable_5b_err_stdlibs.gno b/gnovm/tests/files/addressable_5b_err_stdlibs.gno new file mode 100644 index 00000000000..e2028a22c9e --- /dev/null +++ b/gnovm/tests/files/addressable_5b_err_stdlibs.gno @@ -0,0 +1,11 @@ +package main + +import "std" + +func main() { + // Type constants are not addressable. + _ = &std.BankerTypeReadonly +} + +// Error: +// main/files/addressable_5b_err_stdlibs.gno:7:6: cannot take address of (const (0 std.BankerType)) diff --git a/gnovm/tests/files/addressable_5c_err.gno b/gnovm/tests/files/addressable_5c_err.gno new file mode 100644 index 00000000000..92de1aeb30a --- /dev/null +++ b/gnovm/tests/files/addressable_5c_err.gno @@ -0,0 +1,10 @@ +package main + +const a = 1 + +func main() { + _ = &a +} + +// Error: +// main/files/addressable_5c_err.gno:6:6: cannot take address of (const (1 bigint)) diff --git a/gnovm/tests/files/addressable_5d_err.gno b/gnovm/tests/files/addressable_5d_err.gno new file mode 100644 index 00000000000..fe78ed36681 --- /dev/null +++ b/gnovm/tests/files/addressable_5d_err.gno @@ -0,0 +1,10 @@ +package main + +const a int = 1 + +func main() { + _ = &a +} + +// Error: +// main/files/addressable_5d_err.gno:6:6: cannot take address of (const (1 int)) diff --git a/gnovm/tests/files/addressable_6.gno b/gnovm/tests/files/addressable_6.gno new file mode 100644 index 00000000000..43b1a260eb4 --- /dev/null +++ b/gnovm/tests/files/addressable_6.gno @@ -0,0 +1,27 @@ +package main + +type S struct { + a int +} + +type Alias []int + +func main() { + // Type assertions copy the value being asserted, so only pointers and + // slices are addressable. Slices are addressable because a copy of a slice + // maintains a reference to the same underlying array. + var i any + i = []int{1} + println(&i.([]int)[0]) + + i = &S{} + println(&i.(*S).a) + + i = Alias{4} + println(&i.(Alias)[0]) +} + +// Output: +// &(1 int) +// &(0 int) +// &(4 int) diff --git a/gnovm/tests/files/addressable_6a_err.gno b/gnovm/tests/files/addressable_6a_err.gno new file mode 100644 index 00000000000..09d4f417966 --- /dev/null +++ b/gnovm/tests/files/addressable_6a_err.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var i any + i = 5 + println(&i.(int)) +} + +// Error: +// main/files/addressable_6a_err.gno:6:10: cannot take address of i.((const-type int)) diff --git a/gnovm/tests/files/addressable_6b_err.gno b/gnovm/tests/files/addressable_6b_err.gno new file mode 100644 index 00000000000..308196c254d --- /dev/null +++ b/gnovm/tests/files/addressable_6b_err.gno @@ -0,0 +1,14 @@ +package main + +type S struct { + a int +} + +func main() { + var i any + i = S{a: 9} + println(&i.(S).a) +} + +// Error: +// main/files/addressable_6b_err.gno:10:10: cannot take address of i.(S).a diff --git a/gnovm/tests/files/addressable_6c_err.gno b/gnovm/tests/files/addressable_6c_err.gno new file mode 100644 index 00000000000..76a29346c5f --- /dev/null +++ b/gnovm/tests/files/addressable_6c_err.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var i any + i = [1]int{1} + println(&i.([1]int)[0]) +} + +// Error: +// main/files/addressable_6c_err.gno:6:10: cannot take address of i.([(const (1 int))](const-type int))[(const (0 int))] diff --git a/gnovm/tests/files/addressable_6d_err.gno b/gnovm/tests/files/addressable_6d_err.gno new file mode 100644 index 00000000000..32e658959c4 --- /dev/null +++ b/gnovm/tests/files/addressable_6d_err.gno @@ -0,0 +1,12 @@ +package main + +func main() { + _ = &getSlice().([]int) +} + +func getSlice() any { + return []int{1} +} + +// Error: +// main/files/addressable_6d_err.gno:4:6: cannot take address of getSlice().([](const-type int)) diff --git a/gnovm/tests/files/addressable_7a_err.gno b/gnovm/tests/files/addressable_7a_err.gno new file mode 100644 index 00000000000..f3648b6b990 --- /dev/null +++ b/gnovm/tests/files/addressable_7a_err.gno @@ -0,0 +1,12 @@ +package main + +func foo() ([]int, []string) { + return []int{1, 2, 3}, []string{"a", "b", "c"} +} + +func main() { + _ = &foo() +} + +// Error: +// main/files/addressable_7a_err.gno:8:2: getTypeOf() only supports *CallExpr with 1 result, got ([]int,[]string) diff --git a/gnovm/tests/files/addressable_7b_err.gno b/gnovm/tests/files/addressable_7b_err.gno new file mode 100644 index 00000000000..a621d688ea4 --- /dev/null +++ b/gnovm/tests/files/addressable_7b_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _, _ = &int(9) +} + +// Error: +// main/files/addressable_7b_err.gno:4:9: cannot take address of (const (9 int)) diff --git a/gnovm/tests/files/addressable_8.gno b/gnovm/tests/files/addressable_8.gno new file mode 100644 index 00000000000..1b433c18b9c --- /dev/null +++ b/gnovm/tests/files/addressable_8.gno @@ -0,0 +1,9 @@ +package main + +func main() { + f := func() { println("Hello, World!") } + println(&f) +} + +// Output: +// &(func(){...} func()) diff --git a/gnovm/tests/files/addressable_8a_err.gno b/gnovm/tests/files/addressable_8a_err.gno new file mode 100644 index 00000000000..1be4185ac08 --- /dev/null +++ b/gnovm/tests/files/addressable_8a_err.gno @@ -0,0 +1,8 @@ +package main + +func main() { + _ = &func() { println("Hello, World!") } +} + +// Error: +// main/files/addressable_8a_err.gno:4:6: cannot take address of func func(){ (const (println func(...interface {})))((const ("Hello, World!" string))) } diff --git a/gnovm/tests/files/addressable_9.gno b/gnovm/tests/files/addressable_9.gno new file mode 100644 index 00000000000..3b0b832f781 --- /dev/null +++ b/gnovm/tests/files/addressable_9.gno @@ -0,0 +1,32 @@ +package main + +type S struct { + i int + t *T +} + +type T struct { + i int +} + +func main() { + m := map[int]*S{} + s := &S{i: 4} + m[5] = s + println(&m[5].i) + + mm := map[int]S{} + ss := S{t: new(T)} + mm[8] = ss + println(&mm[8].t.i) + + mmm := map[int]map[int]*S{} + mmm[3] = map[int]*S{} + mmm[3][3] = &S{i: 7} + println(&mmm[3][3].i) +} + +// Output: +// &(4 int) +// &(0 int) +// &(7 int) diff --git a/gnovm/tests/files/addressable_9a_err.gno b/gnovm/tests/files/addressable_9a_err.gno new file mode 100644 index 00000000000..038990e1c01 --- /dev/null +++ b/gnovm/tests/files/addressable_9a_err.gno @@ -0,0 +1,10 @@ +package main + +func main() { + m := map[int]int{} + m[4] = 5 + println(&m[4]) +} + +// Error: +// main/files/addressable_9a_err.gno:6:10: cannot take address of m[(const (4 int))] diff --git a/gnovm/tests/files/addressable_9b_err.gno b/gnovm/tests/files/addressable_9b_err.gno new file mode 100644 index 00000000000..92db1fbeefc --- /dev/null +++ b/gnovm/tests/files/addressable_9b_err.gno @@ -0,0 +1,15 @@ +package main + +type S struct { + i int +} + +func main() { + mmm := map[int]map[int]S{} + mmm[3] = map[int]S{} + mmm[3][3] = S{i: 7} + println(&mmm[3][3].i) +} + +// Error: +// main/files/addressable_9b_err.gno:11:10: cannot take address of mmm[(const (3 int))][(const (3 int))].i diff --git a/gnovm/tests/files/append0.gno b/gnovm/tests/files/append0.gno index 4e6dd601fb7..762f780211f 100644 --- a/gnovm/tests/files/append0.gno +++ b/gnovm/tests/files/append0.gno @@ -2,7 +2,7 @@ package main import "fmt" -func f(a []int, b int) interface{} { return append(a, b) } +func f(a []int, b int) any { return append(a, b) } func main() { a := []int{1, 2} diff --git a/gnovm/tests/files/assign0b.gno b/gnovm/tests/files/assign0b.gno index ed1475f9105..42faa57634d 100644 --- a/gnovm/tests/files/assign0b.gno +++ b/gnovm/tests/files/assign0b.gno @@ -15,5 +15,5 @@ func main() { } // Output: -// &{ 10000000000} -// &{ 0} +// &{ 10s} +// &{ 0s} diff --git a/gnovm/tests/files/assign26.gno b/gnovm/tests/files/assign26.gno index f974ae0b174..7aa6d65ac7f 100644 --- a/gnovm/tests/files/assign26.gno +++ b/gnovm/tests/files/assign26.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} = 1 + var i any = 1 a, b, c := i.(int) } diff --git a/gnovm/tests/files/assign32.gno b/gnovm/tests/files/assign32.gno index 094b08cc713..72d86c6f451 100644 --- a/gnovm/tests/files/assign32.gno +++ b/gnovm/tests/files/assign32.gno @@ -8,7 +8,7 @@ func main() { var mp map[string]int = map[string]int{"idx": 4} var sl []int = []int{4, 5, 6} arr := [1]int{7} - var num interface{} = 5 + var num any = 5 a, b, c, d, e, f, g := int(1), foo(), 3, mp["idx"], num.(int), sl[2], arr[0] println(a, b, c, d, e, f, g) diff --git a/gnovm/tests/files/assign_unnamed_type/more/assgin_interface2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/assgin_interface2_filetest.gno index 129eb8d749a..c30933f45ba 100644 --- a/gnovm/tests/files/assign_unnamed_type/more/assgin_interface2_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/more/assgin_interface2_filetest.gno @@ -4,7 +4,7 @@ type nat []int func main() { var a nat - b := interface{}(nat{1}) + b := any(nat{1}) a = b.(nat) println(b) diff --git a/gnovm/tests/files/assign_unnamed_type/more/default_value_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/default_value_filetest.gno index bb590f60dae..50e85fad890 100644 --- a/gnovm/tests/files/assign_unnamed_type/more/default_value_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/more/default_value_filetest.gno @@ -27,5 +27,5 @@ func main() { // (nil main.nat) // (nil map[int]int) // (nil main.nmap) -// nil func()() +// nil func() // (nil main.nfunc) diff --git a/gnovm/tests/files/assign_unnamed_type/more/method38g_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/method38g_filetest.gno index 2c41bbb0a97..7f44f4c9dfb 100644 --- a/gnovm/tests/files/assign_unnamed_type/more/method38g_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/more/method38g_filetest.gno @@ -5,13 +5,13 @@ type ( Word uint ) -func (n nat) add(ws []Word) interface{} { +func (n nat) add(ws []Word) any { println(ws) return ws } -func (n nat) add2(ws nat) interface{} { +func (n nat) add2(ws nat) any { println(ws) return ws diff --git a/gnovm/tests/files/assign_unnamed_type/more/return_interface1_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/return_interface1_filetest.gno index 61ae8208a4a..7b6be664d96 100644 --- a/gnovm/tests/files/assign_unnamed_type/more/return_interface1_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/more/return_interface1_filetest.gno @@ -1,6 +1,6 @@ package main -func x1() interface{} { +func x1() any { a := "1" return a } @@ -12,4 +12,4 @@ func main() { } // Error: -// main/files/assign_unnamed_type/more/return_interface1_filetest.gno:10:2: cannot use interface{} as uint +// main/files/assign_unnamed_type/more/return_interface1_filetest.gno:10:2: cannot use interface {} as uint diff --git a/gnovm/tests/files/assign_unnamed_type/more/return_interface_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/return_interface_filetest.gno index 11ac22370b5..4fcc90eaa83 100644 --- a/gnovm/tests/files/assign_unnamed_type/more/return_interface_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/more/return_interface_filetest.gno @@ -2,7 +2,7 @@ package main type nat []int -func x() interface{} { +func x() any { a := nat{0} return a } @@ -16,4 +16,4 @@ func main() { } // Error: -// main/files/assign_unnamed_type/more/return_interface_filetest.gno:13:2: cannot use interface{} as []int +// main/files/assign_unnamed_type/more/return_interface_filetest.gno:13:2: cannot use interface {} as []int diff --git a/gnovm/tests/files/assign_unnamed_type/more/return_select_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/return_select_filetest.gno index cf5be970423..1fe608b3d86 100644 --- a/gnovm/tests/files/assign_unnamed_type/more/return_select_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/more/return_select_filetest.gno @@ -2,7 +2,7 @@ package main type nat []int -func x() interface{} { +func x() any { a := nat{0} return a } diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype1c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype1c_filetest.gno index aa5533bd0e8..f1e6655bf89 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype1c_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype1c_filetest.gno @@ -2,11 +2,11 @@ package main type nat []int -func (n nat) zero() interface{} { +func (n nat) zero() any { return []int{0} } -func (n nat) one() interface{} { +func (n nat) one() any { return nat{1} } diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype2c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype2c_filetest.gno index 9fc0e0f06a4..88cfe4f3980 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype2c_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype2c_filetest.gno @@ -2,11 +2,11 @@ package main type nat [1]int -func (n nat) zero() interface{} { +func (n nat) zero() any { return [1]int{0} } -func (n nat) one() interface{} { +func (n nat) one() any { return nat{1} } diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype3c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype3c_filetest.gno index 5d506ab9566..744deede405 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype3c_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype3c_filetest.gno @@ -4,11 +4,11 @@ type nat struct { num int } -func (n nat) zero() interface{} { +func (n nat) zero() any { return struct{ num int }{0} } -func (n nat) one() interface{} { +func (n nat) one() any { return nat{1} } diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype4c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype4c_filetest.gno index d6efcaded75..7243b17a6cf 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype4c_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype4c_filetest.gno @@ -2,11 +2,11 @@ package main type nat map[string]int -func (n nat) zero() interface{} { +func (n nat) zero() any { return map[string]int{"zero": 0} } -func (n nat) one() interface{} { +func (n nat) one() any { return nat{"one": 1} } diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype5_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype5_filetest.gno index 0c15ce1ae02..99470de5eb4 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype5_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype5_filetest.gno @@ -34,8 +34,8 @@ func main() { // Output: // 1 (inc main.op) -// 0 func(n int)( int){...} +// 0 func(int) int{...} // -1 dec -// 0 (func(n int)( int){...} main.op) +// 0 (func(int) int{...} main.op) // -1 dec // 1 inc diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype5a_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype5a_filetest.gno index 165dd8e18a4..b162005192b 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype5a_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype5a_filetest.gno @@ -27,7 +27,7 @@ func main() { } // Output: -// func(n int)( int){...} +// func(int) int{...} // 1 -// (func(n int)( int){...} main.op) +// (func(int) int{...} main.op) // -1 diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype5c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype5c_filetest.gno index aa5533bd0e8..f1e6655bf89 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype5c_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype5c_filetest.gno @@ -2,11 +2,11 @@ package main type nat []int -func (n nat) zero() interface{} { +func (n nat) zero() any { return []int{0} } -func (n nat) one() interface{} { +func (n nat) one() any { return nat{1} } diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype6_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype6_filetest.gno index 41c72c390a1..9f4560da9cb 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype6_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype6_filetest.gno @@ -1,11 +1,11 @@ package main -type nat []interface{} +type nat []any func main() { var a nat - a = []interface{}{0} - b := []interface{}{1} + a = []any{0} + b := []any{1} println(a) println(b) diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype6a_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype6a_filetest.gno index 37e3951df55..818c945747b 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype6a_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype6a_filetest.gno @@ -1,8 +1,8 @@ package main -type nat []interface{} +type nat []any -func (n nat) zero(num []interface{}) { +func (n nat) zero(num []any) { println(num) } @@ -12,10 +12,10 @@ func (n nat) one(num nat) { func main() { var a nat - a = []interface{}{} + a = []any{} println(a) a.zero(nat{0}) - a.one([]interface{}{1}) + a.one([]any{1}) } // Output: diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype6b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype6b_filetest.gno index e57821f8b7d..44a5ed605e4 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype6b_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype6b_filetest.gno @@ -1,18 +1,18 @@ package main -type nat []interface{} +type nat []any func (n nat) zero() nat { - return []interface{}{0} + return []any{0} } -func (n nat) one() []interface{} { +func (n nat) one() []any { return nat{1} } func main() { var a nat - a = []interface{}{} + a = []any{} println(a.zero()) println(a.zero().zero()) diff --git a/gnovm/tests/files/assign_unnamed_type/unnamedtype6c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/unnamedtype6c_filetest.gno index 1c17c204fe8..20d7b9dcff6 100644 --- a/gnovm/tests/files/assign_unnamed_type/unnamedtype6c_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/unnamedtype6c_filetest.gno @@ -1,18 +1,18 @@ package main -type nat []interface{} +type nat []any -func (n nat) zero() interface{} { - return []interface{}{0} +func (n nat) zero() any { + return []any{0} } -func (n nat) one() interface{} { +func (n nat) one() any { return nat{1} } func main() { var a nat - a = []interface{}{} + a = []any{} println(a.zero()) println(a.one()) diff --git a/gnovm/tests/files/blankidentifier1.gno b/gnovm/tests/files/blankidentifier1.gno index 9c93ff08f10..2770e426ae2 100644 --- a/gnovm/tests/files/blankidentifier1.gno +++ b/gnovm/tests/files/blankidentifier1.gno @@ -1,6 +1,6 @@ package main -type zilch interface{} +type zilch any func main() { _ = zilch(nil) diff --git a/gnovm/tests/files/block1.gno b/gnovm/tests/files/block1.gno new file mode 100644 index 00000000000..6eb2403c163 --- /dev/null +++ b/gnovm/tests/files/block1.gno @@ -0,0 +1,669 @@ +// You can edit this code! +// Click here and start typing. +package main + +var v int = 1 + +func main() { + v++ + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Output: +// 3 +// 4 +// 5 +// 6 +// 7 +// 8 +// 9 +// 10 +// 11 +// 12 +// 13 +// 14 +// 15 +// 16 +// 17 +// 18 +// 19 +// 20 +// 21 +// 22 +// 23 +// 24 +// 25 +// 26 +// 27 +// 28 +// 29 +// 30 +// 31 +// 32 +// 33 +// 34 +// 35 +// 36 +// 37 +// 38 +// 39 +// 40 +// 41 +// 42 +// 43 +// 44 +// 45 +// 46 +// 47 +// 48 +// 49 +// 50 +// 51 +// 52 +// 53 +// 54 +// 55 +// 56 +// 57 +// 58 +// 59 +// 60 +// 61 +// 62 +// 63 +// 64 +// 65 +// 66 +// 67 +// 68 +// 69 +// 70 +// 71 +// 72 +// 73 +// 74 +// 75 +// 76 +// 77 +// 78 +// 79 +// 80 +// 81 +// 82 +// 83 +// 84 +// 85 +// 86 +// 87 +// 88 +// 89 +// 90 +// 91 +// 92 +// 93 +// 94 +// 95 +// 96 +// 97 +// 98 +// 99 +// 100 +// 101 +// 102 +// 103 +// 104 +// 105 +// 106 +// 107 +// 108 +// 109 +// 110 +// 111 +// 112 +// 113 +// 114 +// 115 +// 116 +// 117 +// 118 +// 119 +// 120 +// 121 +// 122 +// 123 +// 124 +// 125 +// 126 diff --git a/gnovm/tests/files/block2.gno b/gnovm/tests/files/block2.gno new file mode 100644 index 00000000000..6738c76bf40 --- /dev/null +++ b/gnovm/tests/files/block2.gno @@ -0,0 +1,550 @@ +// You can edit this code! +// Click here and start typing. +package main + +var v int = 1 + +func main() { + v++ + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + + if v >= 1 { + v++ + println(v) + if v >= 1 { + v++ + println(v) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Error: +// main/files/block2.gno:419:129: exceeded maximum VPBlock depth (127) diff --git a/gnovm/tests/files/bool8.gno b/gnovm/tests/files/bool8.gno index 9efbbbe6da2..626504ebeb8 100644 --- a/gnovm/tests/files/bool8.gno +++ b/gnovm/tests/files/bool8.gno @@ -2,14 +2,14 @@ package main // results from comparisons should not be untyped bools -var a interface{} = true +var a any = true func main() { buf := "hello=" isEqual(a, (buf[len(buf)-1] == '=')) } -func isEqual(v1, v2 interface{}) { +func isEqual(v1, v2 any) { println("v1 == v2", v1 == v2) } diff --git a/gnovm/tests/files/cap0.gno b/gnovm/tests/files/cap0.gno index 95e445e1f50..80db86f33a4 100644 --- a/gnovm/tests/files/cap0.gno +++ b/gnovm/tests/files/cap0.gno @@ -1,6 +1,6 @@ package main -func f(a []int) interface{} { +func f(a []int) any { return cap(a) } diff --git a/gnovm/tests/files/closure.gno b/gnovm/tests/files/closure.gno new file mode 100644 index 00000000000..ea05c025954 --- /dev/null +++ b/gnovm/tests/files/closure.gno @@ -0,0 +1,16 @@ +package main + +func main() { + +} + +var a = func() { + b() +} + +var b = func() { + a() +} + +// Error: +// main/files/closure.gno:7:5: constant definition loop with a diff --git a/gnovm/tests/files/comp1.gno b/gnovm/tests/files/comp1.gno index 0a6ca61ca40..631baa9e55b 100644 --- a/gnovm/tests/files/comp1.gno +++ b/gnovm/tests/files/comp1.gno @@ -1,7 +1,7 @@ package main func main() { - var a interface{} = 1 < 2 + var a any = 1 < 2 println(a.(bool)) } diff --git a/gnovm/tests/files/comp4.gno b/gnovm/tests/files/comp4.gno index 501cffeb871..03b823054cf 100644 --- a/gnovm/tests/files/comp4.gno +++ b/gnovm/tests/files/comp4.gno @@ -9,7 +9,7 @@ func main() { println("x == nil", x == nil) println("nil == x", nil == x) - var y interface{} = (*S)(nil) + var y any = (*S)(nil) println("y == y", y == y) println("y == nil", y == nil) println("nil == y", nil == y) diff --git a/gnovm/tests/files/comp5.gno b/gnovm/tests/files/comp5.gno index 827800f2a67..587725bb122 100644 --- a/gnovm/tests/files/comp5.gno +++ b/gnovm/tests/files/comp5.gno @@ -5,8 +5,8 @@ type S struct { func main() { x := (*S)(nil) - var y interface{} = (*S)(nil) - var znil interface{} = nil + var y any = (*S)(nil) + var znil any = nil println("y == znil", y == znil) println("znil == y", znil == y) diff --git a/gnovm/tests/files/const27.gno b/gnovm/tests/files/const27.gno index 4be731e16a7..000bd3b565f 100644 --- a/gnovm/tests/files/const27.gno +++ b/gnovm/tests/files/const27.gno @@ -7,7 +7,7 @@ func v() string { } func main() { - var i interface{} = 1 + var i any = 1 const t, ok = i.(int) fmt.Println(t, ok) } diff --git a/gnovm/tests/files/const31.gno b/gnovm/tests/files/const31.gno index e37577789e6..067dcd1367d 100644 --- a/gnovm/tests/files/const31.gno +++ b/gnovm/tests/files/const31.gno @@ -12,4 +12,4 @@ func main() { } // Error: -// main/files/const31.gno:10:8: (const (v func()( string, string)))() (variable of type (string,string)) is not constant +// main/files/const31.gno:10:8: (const (v func() (string, string)))() (variable of type (string,string)) is not constant diff --git a/gnovm/tests/files/const34.gno b/gnovm/tests/files/const34.gno index 9c79cf86b88..f1c9c806c98 100644 --- a/gnovm/tests/files/const34.gno +++ b/gnovm/tests/files/const34.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/const34.gno:3:7: func func(){ (const (println func(xs ...interface{})()))((const ("hey" string))) } (variable of type func()()) is not constant +// main/files/const34.gno:3:7: func func(){ (const (println func(...interface {})))((const ("hey" string))) } (variable of type func()) is not constant diff --git a/gnovm/tests/files/const39.gno b/gnovm/tests/files/const39.gno index 68ff7dd5630..2e123ca155f 100644 --- a/gnovm/tests/files/const39.gno +++ b/gnovm/tests/files/const39.gno @@ -11,4 +11,4 @@ func main() { } // Error: -// main/files/const39.gno:10:8: T.Mv (variable of type func(tv main.T,a int)( int)) is not constant +// main/files/const39.gno:10:8: T.Mv (variable of type func(main.T, int) int) is not constant diff --git a/gnovm/tests/files/const44.gno b/gnovm/tests/files/const44.gno index 69924b8ea9d..2fe46d3ad21 100644 --- a/gnovm/tests/files/const44.gno +++ b/gnovm/tests/files/const44.gno @@ -1,10 +1,10 @@ package main -const a = interface{}(nil) +const a = any(nil) func main() { println("ok") } // Error: -// main/files/const44.gno:3:7: (const (undefined)) (variable of type interface{}) is not constant +// main/files/const44.gno:3:7: (const (undefined)) (variable of type interface {}) is not constant diff --git a/gnovm/tests/files/const47.gno b/gnovm/tests/files/const47.gno index 528ba469562..02957128b13 100644 --- a/gnovm/tests/files/const47.gno +++ b/gnovm/tests/files/const47.gno @@ -1,6 +1,6 @@ package main -const a = len(map[string][2]interface{}{"arr": {1, 2}}["arr"]) +const a = len(map[string][2]any{"arr": {1, 2}}["arr"]) func main() { println("ok") diff --git a/gnovm/tests/files/const51.gno b/gnovm/tests/files/const51.gno new file mode 100644 index 00000000000..b00748b0ec7 --- /dev/null +++ b/gnovm/tests/files/const51.gno @@ -0,0 +1,20 @@ +package main + +type T1 struct { + x [2]string +} + +type T2 struct { + x *[2]string +} + +func main() { + t1 := T1{x: [2]string{"a", "b"}} + t2 := T2{x: &[2]string{"a", "b"}} + const c1 = len(t1.x) + const c2 = len(t2.x) + println(c1, c2) +} + +// Output: +// 2 2 diff --git a/gnovm/tests/files/const52.gno b/gnovm/tests/files/const52.gno new file mode 100644 index 00000000000..c213faeb12b --- /dev/null +++ b/gnovm/tests/files/const52.gno @@ -0,0 +1,11 @@ +package main + +func main() { + s := make([][2]string, 1) // Slice with length 1 + s[0] = [2]string{"a", "b"} // Assign value to s[0] + const r = len(s[0]) + println(r) // Prints: 2 +} + +// Output: +// 2 diff --git a/gnovm/tests/files/const53.gno b/gnovm/tests/files/const53.gno new file mode 100644 index 00000000000..a4e7c8888f3 --- /dev/null +++ b/gnovm/tests/files/const53.gno @@ -0,0 +1,43 @@ +package main + +type Integer int + +const ( + i0 Integer = 0 + + i int32 = 1 + s = "world" + c = 1 << iota + + s2 = "hey" + d, e = 1, "hello" + + m, n + + b = 1 + 1 + u = +1 + x, y = i0, "hello" +) + +func main() { + println(i) + println(s) + println(c) + println(s2) + println(d, e) + println(m, n) + println(b) + println(u) + println(x, y) +} + +// Output: +// 1 +// world +// 8 +// hey +// 1 hello +// 1 hello +// 2 +// 1 +// (0 main.Integer) hello diff --git a/gnovm/tests/files/const54.gno b/gnovm/tests/files/const54.gno new file mode 100644 index 00000000000..56912217a24 --- /dev/null +++ b/gnovm/tests/files/const54.gno @@ -0,0 +1,14 @@ +package main + +const ( + s int = 1 + g = "world" + c = 1 << iota +) + +func main() { + println(c) +} + +// Output: +// 4 diff --git a/gnovm/tests/files/const55.gno b/gnovm/tests/files/const55.gno new file mode 100644 index 00000000000..9345a890f3c --- /dev/null +++ b/gnovm/tests/files/const55.gno @@ -0,0 +1,14 @@ +package main + +const ( + d, e = 1, "hello" + m +) + +func main() { + println(d, e) + println(m) +} + +// Error: +// main/files/const55.gno:5:2: assignment mismatch: 1 variable(s) but 2 value(s) diff --git a/gnovm/tests/files/const55a.gno b/gnovm/tests/files/const55a.gno new file mode 100644 index 00000000000..5b0cc020a50 --- /dev/null +++ b/gnovm/tests/files/const55a.gno @@ -0,0 +1,14 @@ +package main + +const ( + d, e = 1, "hello" + m, n, l +) + +func main() { + println(d, e) + println(m) +} + +// Error: +// main/files/const55a.gno:5:2: assignment mismatch: 3 variable(s) but 2 value(s) diff --git a/gnovm/tests/files/const56.gno b/gnovm/tests/files/const56.gno new file mode 100644 index 00000000000..609032eed21 --- /dev/null +++ b/gnovm/tests/files/const56.gno @@ -0,0 +1,19 @@ +package main + +const ( + d, e = "hello", 1 + m, n + a string = "a" + k = 1 << iota +) + +func main() { + println(d, e) + println(m, n) + println(k) +} + +// Output: +// hello 1 +// hello 1 +// 8 diff --git a/gnovm/tests/files/const57.gno b/gnovm/tests/files/const57.gno new file mode 100644 index 00000000000..3da1c5fef96 --- /dev/null +++ b/gnovm/tests/files/const57.gno @@ -0,0 +1,23 @@ +package main + +type Value struct { + String string +} + +type S string + +const ( + s S = "hello" + g = "world" + h +) + +func main() { + v := Value{String: "hello"} + println(v.String == g) + println(g == h) +} + +// Output: +// false +// true diff --git a/gnovm/tests/files/const58.gno b/gnovm/tests/files/const58.gno new file mode 100644 index 00000000000..33eda0e03f0 --- /dev/null +++ b/gnovm/tests/files/const58.gno @@ -0,0 +1,13 @@ +package main + +const ( + MaxU int32 = 1<<3 - 1 + MaxUint64 = 18 < 64-1 +) + +func main() { + println(MaxUint64) +} + +// Output: +// true diff --git a/gnovm/tests/files/const59.gno b/gnovm/tests/files/const59.gno new file mode 100644 index 00000000000..82680052da3 --- /dev/null +++ b/gnovm/tests/files/const59.gno @@ -0,0 +1,15 @@ +package main + +const ( + A = ^0 + B = ^1 + C = int8(^1) + D = ^uint8(1) +) + +func main() { + println(A, B, C, D) +} + +// Output: +// -1 -2 -2 254 diff --git a/gnovm/tests/files/const60.gno b/gnovm/tests/files/const60.gno new file mode 100644 index 00000000000..e8ecbe4f3c0 --- /dev/null +++ b/gnovm/tests/files/const60.gno @@ -0,0 +1,12 @@ +package main + +const ( + A = uint(^1) +) + +func main() { + println(A) +} + +// Error: +// main/files/const60.gno:4:6: bigint underflows target kind diff --git a/gnovm/tests/files/const61.gno b/gnovm/tests/files/const61.gno new file mode 100644 index 00000000000..0e75de171d0 --- /dev/null +++ b/gnovm/tests/files/const61.gno @@ -0,0 +1,12 @@ +package main + +const ( + A = uint8(^0) +) + +func main() { + println(A) +} + +// Error: +// main/files/const61.gno:4:6: bigint underflows target kind diff --git a/gnovm/tests/files/convert4.gno b/gnovm/tests/files/convert4.gno index e03e1d07ce3..03c7d454b82 100644 --- a/gnovm/tests/files/convert4.gno +++ b/gnovm/tests/files/convert4.gno @@ -5,4 +5,4 @@ func main() { } // Error: -// main/files/convert4.gno:4:10: cannot convert (undefined) to int +// main/files/convert4.gno:4:10: cannot convert (const (undefined)) to IntKind diff --git a/gnovm/tests/files/convert5.gno b/gnovm/tests/files/convert5.gno index e2e16c5eb83..1e785abdd7b 100644 --- a/gnovm/tests/files/convert5.gno +++ b/gnovm/tests/files/convert5.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/convert5.gno:3:1: cannot convert (undefined) to int +// main/files/convert5.gno:3:1: cannot convert (const (undefined)) to IntKind diff --git a/gnovm/tests/files/convert6.gno b/gnovm/tests/files/convert6.gno new file mode 100644 index 00000000000..25b84aea24e --- /dev/null +++ b/gnovm/tests/files/convert6.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const a int = -1 + println(uint(a)) +} + +// Error: +// main/files/convert6.gno:5:10: cannot convert constant of type IntKind to UintKind diff --git a/gnovm/tests/files/convert6a.gno b/gnovm/tests/files/convert6a.gno new file mode 100644 index 00000000000..cc06523eca7 --- /dev/null +++ b/gnovm/tests/files/convert6a.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const a int = -1 + println(uint8(a)) +} + +// Error: +// main/files/convert6a.gno:5:10: cannot convert constant of type IntKind to Uint8Kind diff --git a/gnovm/tests/files/convert6b.gno b/gnovm/tests/files/convert6b.gno new file mode 100644 index 00000000000..19fbba47154 --- /dev/null +++ b/gnovm/tests/files/convert6b.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const a int = -1 + println(uint16(a)) +} + +// Error: +// main/files/convert6b.gno:5:10: cannot convert constant of type IntKind to Uint16Kind diff --git a/gnovm/tests/files/convert6c.gno b/gnovm/tests/files/convert6c.gno new file mode 100644 index 00000000000..895196017d1 --- /dev/null +++ b/gnovm/tests/files/convert6c.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const a int = -1 + println(uint32(a)) +} + +// Error: +// main/files/convert6c.gno:5:10: cannot convert constant of type IntKind to Uint32Kind diff --git a/gnovm/tests/files/convert6d.gno b/gnovm/tests/files/convert6d.gno new file mode 100644 index 00000000000..0386e25dccf --- /dev/null +++ b/gnovm/tests/files/convert6d.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const a int = -1 + println(uint64(a)) +} + +// Error: +// main/files/convert6d.gno:5:10: cannot convert constant of type IntKind to Uint64Kind diff --git a/gnovm/tests/files/convert6e.gno b/gnovm/tests/files/convert6e.gno new file mode 100644 index 00000000000..fc533b2abc6 --- /dev/null +++ b/gnovm/tests/files/convert6e.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const a float32 = 1.5 + println(int32(a)) +} + +// Error: +// main/files/convert6e.gno:5:10: cannot convert constant of type Float32Kind to Int32Kind diff --git a/gnovm/tests/files/convert7.gno b/gnovm/tests/files/convert7.gno new file mode 100644 index 00000000000..ec4e1aa5f95 --- /dev/null +++ b/gnovm/tests/files/convert7.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(int32(1.5)) +} + +// Error: +// main/files/convert7.gno:4:10: cannot convert (const (1.5 bigdec)) to integer type diff --git a/gnovm/tests/files/convert7a.gno b/gnovm/tests/files/convert7a.gno new file mode 100644 index 00000000000..533205378e2 --- /dev/null +++ b/gnovm/tests/files/convert7a.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const a float64 = 1.5 + println(int64(a)) +} + +// Error: +// main/files/convert7a.gno:5:10: cannot convert constant of type Float64Kind to Int64Kind diff --git a/gnovm/tests/files/convert7b.gno b/gnovm/tests/files/convert7b.gno new file mode 100644 index 00000000000..7754acdb32c --- /dev/null +++ b/gnovm/tests/files/convert7b.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(int64(1.5)) +} + +// Error: +// main/files/convert7b.gno:4:10: cannot convert (const (1.5 bigdec)) to integer type diff --git a/gnovm/tests/files/convert7c.gno b/gnovm/tests/files/convert7c.gno new file mode 100644 index 00000000000..b7d88c6cd98 --- /dev/null +++ b/gnovm/tests/files/convert7c.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const f = float64(1.0) + println(int64(f)) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/copy2.gno b/gnovm/tests/files/copy2.gno index 798f32f0969..85a0d698734 100644 --- a/gnovm/tests/files/copy2.gno +++ b/gnovm/tests/files/copy2.gno @@ -2,7 +2,7 @@ package main import "fmt" -func f(a, b []int) interface{} { return copy(a, b) } +func f(a, b []int) any { return copy(a, b) } func main() { a := []int{10, 20, 30} diff --git a/gnovm/tests/files/extern/foo/internal/aa/aa.gno b/gnovm/tests/files/extern/foo/internal/aa/aa.gno new file mode 100644 index 00000000000..477b3b21412 --- /dev/null +++ b/gnovm/tests/files/extern/foo/internal/aa/aa.gno @@ -0,0 +1,3 @@ +package aa + +const A = 456 diff --git a/gnovm/tests/files/extern/foo/internal/internal.gno b/gnovm/tests/files/extern/foo/internal/internal.gno new file mode 100644 index 00000000000..646402b1242 --- /dev/null +++ b/gnovm/tests/files/extern/foo/internal/internal.gno @@ -0,0 +1,8 @@ +package internal + +import "github.com/gnolang/gno/_test/foo/internal/aa" + +const ( + A = 123 + B = aa.A +) diff --git a/gnovm/tests/files/extern/timtadh/data_structures/tree/avl/avltree.gno b/gnovm/tests/files/extern/timtadh/data_structures/tree/avl/avltree.gno index 7f7161063c9..9253fb625cc 100644 --- a/gnovm/tests/files/extern/timtadh/data_structures/tree/avl/avltree.gno +++ b/gnovm/tests/files/extern/timtadh/data_structures/tree/avl/avltree.gno @@ -43,16 +43,16 @@ func (self *AvlTree) Has(key types.Hashable) bool { return self.root.Has(key) } -func (self *AvlTree) Put(key types.Hashable, value interface{}) (err error) { +func (self *AvlTree) Put(key types.Hashable, value any) (err error) { self.root, _ = self.root.Put(key, value) return nil } -func (self *AvlTree) Get(key types.Hashable) (value interface{}, err error) { +func (self *AvlTree) Get(key types.Hashable) (value any, err error) { return self.root.Get(key) } -func (self *AvlTree) Remove(key types.Hashable) (value interface{}, err error) { +func (self *AvlTree) Remove(key types.Hashable) (value any, err error) { new_root, value, err := self.root.Remove(key) if err != nil { return nil, err @@ -79,7 +79,7 @@ func (self *AvlTree) Keys() types.KIterator { type AvlNode struct { key types.Hashable - value interface{} + value any height int left *AvlNode right *AvlNode @@ -98,7 +98,7 @@ func (self *AvlNode) Has(key types.Hashable) (has bool) { } } -func (self *AvlNode) Get(key types.Hashable) (value interface{}, err error) { +func (self *AvlNode) Get(key types.Hashable) (value any, err error) { if self == nil { return nil, fmt.Errorf("%v", key) } @@ -209,7 +209,7 @@ func (self *AvlNode) balance() *AvlNode { return self } -func (self *AvlNode) Put(key types.Hashable, value interface{}) (_ *AvlNode, updated bool) { +func (self *AvlNode) Put(key types.Hashable, value any) (_ *AvlNode, updated bool) { if self == nil { return &AvlNode{key: key, value: value, height: 1}, false } @@ -231,7 +231,7 @@ func (self *AvlNode) Put(key types.Hashable, value interface{}) (_ *AvlNode, upd return self, updated } -func (self *AvlNode) Remove(key types.Hashable) (_ *AvlNode, value interface{}, err error) { +func (self *AvlNode) Remove(key types.Hashable) (_ *AvlNode, value any, err error) { if self == nil { return nil, nil, fmt.Errorf("%v", key) } @@ -289,7 +289,7 @@ func (self *AvlNode) Key() types.Hashable { return self.key } -func (self *AvlNode) Value() interface{} { +func (self *AvlNode) Value() any { return self.value } diff --git a/gnovm/tests/files/extern/timtadh/data_structures/types/map_entry.gno b/gnovm/tests/files/extern/timtadh/data_structures/types/map_entry.gno index 2c1567396c2..0dc20cdf5ff 100644 --- a/gnovm/tests/files/extern/timtadh/data_structures/types/map_entry.gno +++ b/gnovm/tests/files/extern/timtadh/data_structures/types/map_entry.gno @@ -6,7 +6,7 @@ import ( type MapEntry struct { Key Hashable - Value interface{} + Value any } func (m *MapEntry) Equals(other Equatable) bool { diff --git a/gnovm/tests/files/extern/timtadh/data_structures/types/types.gno b/gnovm/tests/files/extern/timtadh/data_structures/types/types.gno index daa6e57e24e..539a65a1f50 100644 --- a/gnovm/tests/files/extern/timtadh/data_structures/types/types.gno +++ b/gnovm/tests/files/extern/timtadh/data_structures/types/types.gno @@ -21,10 +21,10 @@ type MHashable interface { } type ( - Iterator func() (item interface{}, next Iterator) + Iterator func() (item any, next Iterator) KIterator func() (key Hashable, next KIterator) - KVIterator func() (key Hashable, value interface{}, next KVIterator) - Coroutine func(send interface{}) (recv interface{}, next Coroutine) + KVIterator func() (key Hashable, value any, next KVIterator) + Coroutine func(send any) (recv any, next Coroutine) ) type Iterable interface { @@ -56,19 +56,19 @@ type Sized interface { type MapOperable interface { Sized Has(key Hashable) bool - Put(key Hashable, value interface{}) (err error) - Get(key Hashable) (value interface{}, err error) - Remove(key Hashable) (value interface{}, err error) + Put(key Hashable, value any) (err error) + Get(key Hashable) (value any, err error) + Remove(key Hashable) (value any, err error) } -type WhereFunc func(value interface{}) bool +type WhereFunc func(value any) bool type MultiMapOperable interface { Sized Has(key Hashable) bool Count(key Hashable) int - Add(key Hashable, value interface{}) (err error) - Replace(key Hashable, where WhereFunc, value interface{}) (err error) + Add(key Hashable, value any) (err error) + Replace(key Hashable, where WhereFunc, value any) (err error) Find(key Hashable) KVIterator RemoveWhere(key Hashable, where WhereFunc) (err error) } @@ -199,7 +199,7 @@ type TreeMap interface { type TreeNode interface { IsNil() bool Key() Hashable - Value() interface{} + Value() any Children() TreeNodeIterator GetChild(int) TreeNode // if your tree can't support this simply panic // many of the utility functions do not require this diff --git a/gnovm/tests/files/extern/timtadh/data_structures/types/util.gno b/gnovm/tests/files/extern/timtadh/data_structures/types/util.gno index a1bc7a1284c..4b57ba19f03 100644 --- a/gnovm/tests/files/extern/timtadh/data_structures/types/util.gno +++ b/gnovm/tests/files/extern/timtadh/data_structures/types/util.gno @@ -7,7 +7,7 @@ func IsNil(object TreeNode) bool { func MakeKVIteratorFromTreeNodeIterator(tni TreeNodeIterator) KVIterator { var kv_iterator KVIterator - kv_iterator = func() (key Hashable, value interface{}, next KVIterator) { + kv_iterator = func() (key Hashable, value any, next KVIterator) { var tn TreeNode tn, tni = tni() if tni == nil { @@ -59,7 +59,7 @@ func MakeKeysIterator(obj KVIterable) KIterator { func MakeValuesIterator(obj KVIterable) Iterator { kv_iterator := obj.Iterate() var v_iterator Iterator - v_iterator = func() (value interface{}, next Iterator) { + v_iterator = func() (value any, next Iterator) { _, value, kv_iterator = kv_iterator() if kv_iterator == nil { return nil, nil @@ -73,7 +73,7 @@ func MakeItemsIterator(obj KVIterable) (kit KIterator) { kv_iterator := obj.Iterate() kit = func() (item Hashable, next KIterator) { var key Hashable - var value interface{} + var value any key, value, kv_iterator = kv_iterator() if kv_iterator == nil { return nil, nil diff --git a/gnovm/tests/files/fun10.gno b/gnovm/tests/files/fun10.gno index 55896395775..c649da0825c 100644 --- a/gnovm/tests/files/fun10.gno +++ b/gnovm/tests/files/fun10.gno @@ -13,5 +13,5 @@ func main() { } // Output: -// nil func()() +// nil func() // nil func diff --git a/gnovm/tests/files/fun12.gno b/gnovm/tests/files/fun12.gno index f47d3ffc72e..402fc07bc0e 100644 --- a/gnovm/tests/files/fun12.gno +++ b/gnovm/tests/files/fun12.gno @@ -1,9 +1,9 @@ package main -func use(interface{}) {} +func use(any) {} func main() { - z := map[string]interface{}{"a": 5} + z := map[string]any{"a": 5} use(z) println("bye") } diff --git a/gnovm/tests/files/fun13.gno b/gnovm/tests/files/fun13.gno index ab9d0037d87..70cedebb9d4 100644 --- a/gnovm/tests/files/fun13.gno +++ b/gnovm/tests/files/fun13.gno @@ -8,7 +8,7 @@ func newT() (T, error) { return T{}, nil } func main() { var ( - i interface{} + i any err error ) i, err = newT() diff --git a/gnovm/tests/files/fun15.gno b/gnovm/tests/files/fun15.gno index 68da7fbc2f0..137f8d34d04 100644 --- a/gnovm/tests/files/fun15.gno +++ b/gnovm/tests/files/fun15.gno @@ -1,6 +1,6 @@ package main -func f1(a int) interface{} { return a + 1 } +func f1(a int) any { return a + 1 } func main() { c := f1(3) diff --git a/gnovm/tests/files/fun16.gno b/gnovm/tests/files/fun16.gno index 631469df722..2d4879ca909 100644 --- a/gnovm/tests/files/fun16.gno +++ b/gnovm/tests/files/fun16.gno @@ -2,7 +2,7 @@ package main func f1(a int) int { return a + 1 } -func f2(a int) interface{} { return f1(a) } +func f2(a int) any { return f1(a) } func main() { c := f2(3) diff --git a/gnovm/tests/files/fun17.gno b/gnovm/tests/files/fun17.gno index fbfaa8980e2..bd71aebc101 100644 --- a/gnovm/tests/files/fun17.gno +++ b/gnovm/tests/files/fun17.gno @@ -1,8 +1,8 @@ package main -func f1(a int) interface{} { return a + 1 } +func f1(a int) any { return a + 1 } -func f2(a int) interface{} { return f1(a) } +func f2(a int) any { return f1(a) } func main() { c := f2(3) diff --git a/gnovm/tests/files/fun18.gno b/gnovm/tests/files/fun18.gno index 4f705b39f5e..81f1a63390f 100644 --- a/gnovm/tests/files/fun18.gno +++ b/gnovm/tests/files/fun18.gno @@ -2,7 +2,7 @@ package main var m = map[string]int{"foo": 1, "bar": 2} -func f(s string) interface{} { return m[s] } +func f(s string) any { return m[s] } func main() { println(f("foo").(int)) diff --git a/gnovm/tests/files/fun19b.gno b/gnovm/tests/files/fun19b.gno index 30c951003f4..285a9b8fb79 100644 --- a/gnovm/tests/files/fun19b.gno +++ b/gnovm/tests/files/fun19b.gno @@ -4,7 +4,7 @@ import ( "fmt" ) -func foo() ([]string, interface{}) { +func foo() ([]string, any) { return nil, fmt.Errorf("bar") } diff --git a/gnovm/tests/files/fun20b.gno b/gnovm/tests/files/fun20b.gno index 4657fcc331a..e548b4830e6 100644 --- a/gnovm/tests/files/fun20b.gno +++ b/gnovm/tests/files/fun20b.gno @@ -2,11 +2,11 @@ package main import "fmt" -var myerr interface{} = fmt.Errorf("bar") +var myerr any = fmt.Errorf("bar") -func ferr() interface{} { return myerr } +func ferr() any { return myerr } -func foo() ([]string, interface{}) { +func foo() ([]string, any) { return nil, ferr() } diff --git a/gnovm/tests/files/heap_alloc_forloop1.gno b/gnovm/tests/files/heap_alloc_forloop1.gno index c166bf8e167..c746dd2e215 100644 --- a/gnovm/tests/files/heap_alloc_forloop1.gno +++ b/gnovm/tests/files/heap_alloc_forloop1.gno @@ -23,7 +23,7 @@ func main() { // go 1.22 loop var is not supported for now. // Preprocessed: -// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), (const-type gonative{interface {}})(i), (const-type gonative{interface {}})(*(e))) } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { s1 = (const (append func(x []*int,args ...*int)(res []*int)))(s1, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } } +// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { s1 = (const (append func([]*int, ...*int) []*int))(s1, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } } // Output: // s1[0] is: 3 diff --git a/gnovm/tests/files/heap_alloc_forloop1a.gno b/gnovm/tests/files/heap_alloc_forloop1a.gno index 6d0895902bd..82e7df17e1d 100644 --- a/gnovm/tests/files/heap_alloc_forloop1a.gno +++ b/gnovm/tests/files/heap_alloc_forloop1a.gno @@ -29,7 +29,7 @@ func main() { // go 1.22 loop var is not supported for now. // Preprocessed: -// file{ package main; import fmt fmt; type Int (const-type main.Int); var s1 []*(Int); func inc2(j *(Int)) { *(j) = *(j) + (const (2 main.Int)) }; func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), (const-type gonative{interface {}})(i), (const-type gonative{interface {}})(*(e))) } }(); for i := (const (0 main.Int)); i<~VPBlock(1,0)> < (const (10 main.Int)); inc2(&(i<~VPBlock(1,0)>)) { s1 = (const (append func(x []*main.Int,args ...*main.Int)(res []*main.Int)))(s1, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } } +// file{ package main; import fmt fmt; type Int (const-type main.Int); var s1 []*(Int); func inc2(j *(Int)) { *(j) = *(j) + (const (2 main.Int)) }; func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 main.Int)); i<~VPBlock(1,0)> < (const (10 main.Int)); inc2(&(i<~VPBlock(1,0)>)) { s1 = (const (append func([]*main.Int, ...*main.Int) []*main.Int))(s1, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } } // Output: // s1[0] is: 10 diff --git a/gnovm/tests/files/heap_alloc_forloop1b.gno b/gnovm/tests/files/heap_alloc_forloop1b.gno index 35b167e4168..de8217003a5 100644 --- a/gnovm/tests/files/heap_alloc_forloop1b.gno +++ b/gnovm/tests/files/heap_alloc_forloop1b.gno @@ -26,7 +26,7 @@ func main() { // go 1.22 loop var is not supported for now. // Preprocessed: -// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), (const-type gonative{interface {}})(i), (const-type gonative{interface {}})(*(e))) } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { r := i<~VPBlock(1,0)>; r, ok := (const (0 int)), (const (true bool)); (const (println func(xs ...interface{})()))(ok, r); s1 = (const (append func(x []*int,args ...*int)(res []*int)))(s1, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } } +// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { r := i<~VPBlock(1,0)>; r, ok := (const (0 int)), (const (true bool)); (const (println func(...interface {})))(ok, r); s1 = (const (append func([]*int, ...*int) []*int))(s1, &(i<~VPBlock(1,0)>)) } }; func main() { forLoopRef() } } // Output: // true 0 diff --git a/gnovm/tests/files/heap_alloc_forloop2.gno b/gnovm/tests/files/heap_alloc_forloop2.gno index aa7f6e44dd3..43bd6259b36 100644 --- a/gnovm/tests/files/heap_alloc_forloop2.gno +++ b/gnovm/tests/files/heap_alloc_forloop2.gno @@ -25,7 +25,7 @@ func main() { // You can tell by the preprocess printout of z and z<~...>. // Preprocessed: -// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), (const-type gonative{interface {}})(i), (const-type gonative{interface {}})(*(e))) } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i + (const (1 int)); s1 = (const (append func(x []*int,args ...*int)(res []*int)))(s1, &(z<~VPBlock(1,1)>)) } }; func main() { forLoopRef() } } +// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i + (const (1 int)); s1 = (const (append func([]*int, ...*int) []*int))(s1, &(z<~VPBlock(1,1)>)) } }; func main() { forLoopRef() } } // Output: // s1[0] is: 1 diff --git a/gnovm/tests/files/heap_alloc_forloop2a.gno b/gnovm/tests/files/heap_alloc_forloop2a.gno index be4b089ccad..7e9b59b4451 100644 --- a/gnovm/tests/files/heap_alloc_forloop2a.gno +++ b/gnovm/tests/files/heap_alloc_forloop2a.gno @@ -26,7 +26,7 @@ func main() { // You can tell by the preprocess printout of z and z<~...>. // Preprocessed: -// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), (const-type gonative{interface {}})(i), (const-type gonative{interface {}})(*(e))) } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; s1 = (const (append func(x []*int,args ...*int)(res []*int)))(s1, &(z<~VPBlock(1,1)>)); z<~VPBlock(1,1)>++ } }; func main() { forLoopRef() } } +// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; s1 = (const (append func([]*int, ...*int) []*int))(s1, &(z<~VPBlock(1,1)>)); z<~VPBlock(1,1)>++ } }; func main() { forLoopRef() } } // Output: // s1[0] is: 1 diff --git a/gnovm/tests/files/heap_alloc_forloop3.gno b/gnovm/tests/files/heap_alloc_forloop3.gno index 91c9b627120..8fd2da59c37 100644 --- a/gnovm/tests/files/heap_alloc_forloop3.gno +++ b/gnovm/tests/files/heap_alloc_forloop3.gno @@ -25,7 +25,7 @@ func main() { // You can tell by the preprocess printout of z and z<()~...>. // Preprocessed: -// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; fs = (const (append func(x []main.f,args ...main.f)(res []main.f)))(fs, (const-type main.f)(func func(){ (const (println func(xs ...interface{})()))(z<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } } +// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; fs = (const (append func([]main.f, ...main.f) []main.f))(fs, (const-type main.f)(func func(){ (const (println func(...interface {})))(z<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_forloop3a.gno b/gnovm/tests/files/heap_alloc_forloop3a.gno index fd361e7134e..c8504cc58a5 100644 --- a/gnovm/tests/files/heap_alloc_forloop3a.gno +++ b/gnovm/tests/files/heap_alloc_forloop3a.gno @@ -27,7 +27,7 @@ func main() { // You can tell by the preprocess printout of z and z<()~...>. // Preprocessed: -// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { x := i; (const (println func(xs ...interface{})()))(x); z := i; fs = (const (append func(x []main.f,args ...main.f)(res []main.f)))(fs, (const-type main.f)(func func(){ (const (println func(xs ...interface{})()))(z<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } } +// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { x := i; (const (println func(...interface {})))(x); z := i; fs = (const (append func([]main.f, ...main.f) []main.f))(fs, (const-type main.f)(func func(){ (const (println func(...interface {})))(z<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_forloop4.gno b/gnovm/tests/files/heap_alloc_forloop4.gno index 3cddb1a60fe..7ddcea37d25 100644 --- a/gnovm/tests/files/heap_alloc_forloop4.gno +++ b/gnovm/tests/files/heap_alloc_forloop4.gno @@ -23,7 +23,7 @@ func main() { // go 1.22 loop var is not supported for now. // Preprocessed: -// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { fs = (const (append func(x []main.f,args ...main.f)(res []main.f)))(fs, (const-type main.f)(func func(){ (const (println func(xs ...interface{})()))(i<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } } +// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { fs = (const (append func([]main.f, ...main.f) []main.f))(fs, (const-type main.f)(func func(){ (const (println func(...interface {})))(i<~VPBlock(1,0)>) }>)) } }; func main() { forLoopClosure() } } // Output: // 3 diff --git a/gnovm/tests/files/heap_alloc_forloop5.gno b/gnovm/tests/files/heap_alloc_forloop5.gno index 4f563ec866b..1e53e6bbe1f 100644 --- a/gnovm/tests/files/heap_alloc_forloop5.gno +++ b/gnovm/tests/files/heap_alloc_forloop5.gno @@ -24,7 +24,7 @@ func main() { } // Preprocessed: -// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { fs = (const (append func(x []main.f,args ...main.f)(res []main.f)))(fs, (const-type main.f)(func func(){ z := i<~VPBlock(1,1)>; (const (println func(xs ...interface{})()))(z) }>)) } }; func main() { forLoopClosure() } } +// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { fs = (const (append func([]main.f, ...main.f) []main.f))(fs, (const-type main.f)(func func(){ z := i<~VPBlock(1,1)>; (const (println func(...interface {})))(z) }>)) } }; func main() { forLoopClosure() } } // Output: // 3 diff --git a/gnovm/tests/files/heap_alloc_forloop5a.gno b/gnovm/tests/files/heap_alloc_forloop5a.gno index 039be2b86a8..9664ca18e85 100644 --- a/gnovm/tests/files/heap_alloc_forloop5a.gno +++ b/gnovm/tests/files/heap_alloc_forloop5a.gno @@ -25,7 +25,7 @@ func main() { } // Preprocessed: -// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { x := i; fs = (const (append func(x []main.f,args ...main.f)(res []main.f)))(fs, (const-type main.f)(func func(){ z := x<~VPBlock(1,1)>; (const (println func(xs ...interface{})()))(z) }>)) } }; func main() { forLoopClosure() } } +// file{ package main; type f (const-type main.f); var fs []f; func forLoopClosure() { defer func func(){ for _, f := range fs { f() } }(); for i := (const (0 int)); i < (const (3 int)); i++ { x := i; fs = (const (append func([]main.f, ...main.f) []main.f))(fs, (const-type main.f)(func func(){ z := x<~VPBlock(1,1)>; (const (println func(...interface {})))(z) }>)) } }; func main() { forLoopClosure() } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_forloop6.gno b/gnovm/tests/files/heap_alloc_forloop6.gno index 6cfa8a65fc8..1177626ac73 100644 --- a/gnovm/tests/files/heap_alloc_forloop6.gno +++ b/gnovm/tests/files/heap_alloc_forloop6.gno @@ -17,7 +17,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; f := func func() (const-type int){ return z<~VPBlock(1,1)> }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (3 int)); i++ { z := i; f := func func() (const-type int){ return z<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_forloop6a.gno b/gnovm/tests/files/heap_alloc_forloop6a.gno index 6365fcd8c62..c465a8cef03 100644 --- a/gnovm/tests/files/heap_alloc_forloop6a.gno +++ b/gnovm/tests/files/heap_alloc_forloop6a.gno @@ -16,7 +16,7 @@ func main() { // go 1.22 loop var is not supported for now. // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (5 int)); i<~VPBlock(1,0)>++ { f := func func() (const-type int){ return i<~VPBlock(1,1)> }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (5 int)); i<~VPBlock(1,0)>++ { f := func func() (const-type int){ return i<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 5 diff --git a/gnovm/tests/files/heap_alloc_forloop6b.gno b/gnovm/tests/files/heap_alloc_forloop6b.gno index 73d9e5cd6a7..2e77a580ce8 100644 --- a/gnovm/tests/files/heap_alloc_forloop6b.gno +++ b/gnovm/tests/files/heap_alloc_forloop6b.gno @@ -18,7 +18,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; y := (const (0 int)); f := func func() (const-type int){ x<~VPBlock(1,1)> += y<~VPBlock(1,2)>; x<~VPBlock(1,1)> += (const (1 int)); return x<~VPBlock(1,1)> }, y<()~VPBlock(1,2)>>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; y := (const (0 int)); f := func func() (const-type int){ x<~VPBlock(1,1)> += y<~VPBlock(1,2)>; x<~VPBlock(1,1)> += (const (1 int)); return x<~VPBlock(1,1)> }, y<()~VPBlock(1,2)>>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_forloop6c.gno b/gnovm/tests/files/heap_alloc_forloop6c.gno index f8d2d410f6c..1d6a372b8da 100644 --- a/gnovm/tests/files/heap_alloc_forloop6c.gno +++ b/gnovm/tests/files/heap_alloc_forloop6c.gno @@ -16,7 +16,7 @@ func main() { // go 1.22 loop var is not supported for now. // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (2 int)); i<~VPBlock(1,0)>++ { f := func func() (const-type int){ return i<~VPBlock(1,1)> }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (2 int)); i<~VPBlock(1,0)>++ { f := func func() (const-type int){ return i<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 2 diff --git a/gnovm/tests/files/heap_alloc_forloop6f.gno b/gnovm/tests/files/heap_alloc_forloop6f.gno index fcc2cdfdcc1..29001216dd4 100644 --- a/gnovm/tests/files/heap_alloc_forloop6f.gno +++ b/gnovm/tests/files/heap_alloc_forloop6f.gno @@ -16,7 +16,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (5 int)); i++ { var x (const-type int); f := func func() (const-type int){ return x<~VPBlock(1,1)> }>; x<~VPBlock(1,1)> = i; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (5 int)); i++ { var x (const-type int); f := func func() (const-type int){ return x<~VPBlock(1,1)> }>; x<~VPBlock(1,1)> = i; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_forloop6g.gno b/gnovm/tests/files/heap_alloc_forloop6g.gno index 4ff7856c97c..fb0a9936fcd 100644 --- a/gnovm/tests/files/heap_alloc_forloop6g.gno +++ b/gnovm/tests/files/heap_alloc_forloop6g.gno @@ -17,7 +17,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (5 int)); i++ { x := i; { f := func func() (const-type int){ return x<~VPBlock(1,1)> }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) } }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (5 int)); i++ { x := i; { f := func func() (const-type int){ return x<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) } }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_forloop6h.gno b/gnovm/tests/files/heap_alloc_forloop6h.gno index 75b84bebf91..e1b2adb6e31 100644 --- a/gnovm/tests/files/heap_alloc_forloop6h.gno +++ b/gnovm/tests/files/heap_alloc_forloop6h.gno @@ -18,7 +18,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; for j := (const (0 int)); j < (const (2 int)); j++ { y := j; f := func func() (const-type int){ return x<~VPBlock(1,1)> + y<~VPBlock(1,2)> }, y<()~VPBlock(1,1)>>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) } }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; for j := (const (0 int)); j < (const (2 int)); j++ { y := j; f := func func() (const-type int){ return x<~VPBlock(1,1)> + y<~VPBlock(1,2)> }, y<()~VPBlock(1,1)>>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) } }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Go Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_forloop6h0.gno b/gnovm/tests/files/heap_alloc_forloop6h0.gno index 0225bd62cf6..0ccb6edfbc3 100644 --- a/gnovm/tests/files/heap_alloc_forloop6h0.gno +++ b/gnovm/tests/files/heap_alloc_forloop6h0.gno @@ -18,7 +18,7 @@ func main() { // go 1.22 loop var is not supported for now. // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (2 int)); i<~VPBlock(1,0)>++ { for j := (const (0 int)); j<~VPBlock(1,0)> < (const (2 int)); j<~VPBlock(1,0)>++ { f := func func() (const-type int){ return i<~VPBlock(1,1)> + j<~VPBlock(1,2)> }, j<()~VPBlock(1,0)>>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) } }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (2 int)); i<~VPBlock(1,0)>++ { for j := (const (0 int)); j<~VPBlock(1,0)> < (const (2 int)); j<~VPBlock(1,0)>++ { f := func func() (const-type int){ return i<~VPBlock(1,1)> + j<~VPBlock(1,2)> }, j<()~VPBlock(1,0)>>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) } }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 4 diff --git a/gnovm/tests/files/heap_alloc_forloop6i.gno b/gnovm/tests/files/heap_alloc_forloop6i.gno index 28bb17cbf5f..998a5a59f5f 100644 --- a/gnovm/tests/files/heap_alloc_forloop6i.gno +++ b/gnovm/tests/files/heap_alloc_forloop6i.gno @@ -19,7 +19,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); var x (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x = i; for j := (const (0 int)); j < (const (2 int)); j++ { y := j; f := func func() (const-type int){ return x + y<~VPBlock(1,1)> }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) } }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); var x (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x = i; for j := (const (0 int)); j < (const (2 int)); j++ { y := j; f := func func() (const-type int){ return x + y<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) } }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Go Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_forloop7.gno b/gnovm/tests/files/heap_alloc_forloop7.gno index 95fdf42045d..783ff92ce39 100644 --- a/gnovm/tests/files/heap_alloc_forloop7.gno +++ b/gnovm/tests/files/heap_alloc_forloop7.gno @@ -21,7 +21,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; f := func func() (const-type int){ if (const (true bool)) { if (const (true bool)) { return x<~VPBlock(3,1)> } }; return (const (0 int)) }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; f := func func() (const-type int){ if (const (true bool)) { if (const (true bool)) { return x<~VPBlock(3,1)> } }; return (const (0 int)) }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_forloop7a.gno b/gnovm/tests/files/heap_alloc_forloop7a.gno index 59e83022f57..54a8f2afc9a 100644 --- a/gnovm/tests/files/heap_alloc_forloop7a.gno +++ b/gnovm/tests/files/heap_alloc_forloop7a.gno @@ -19,7 +19,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; f := func func() (const-type int){ if (const (true bool)) { return x<~VPBlock(2,1)> }; return (const (0 int)) }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; f := func func() (const-type int){ if (const (true bool)) { return x<~VPBlock(2,1)> }; return (const (0 int)) }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_forloop8.gno b/gnovm/tests/files/heap_alloc_forloop8.gno index 7c86fdc5b90..28c61a15af5 100644 --- a/gnovm/tests/files/heap_alloc_forloop8.gno +++ b/gnovm/tests/files/heap_alloc_forloop8.gno @@ -18,7 +18,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; f := func func() (const-type int){ { return x<~VPBlock(2,1)> } }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; f := func func() (const-type int){ { return x<~VPBlock(2,1)> } }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_forloop8a.gno b/gnovm/tests/files/heap_alloc_forloop8a.gno index f0864fa07da..fe1c4b906a4 100644 --- a/gnovm/tests/files/heap_alloc_forloop8a.gno +++ b/gnovm/tests/files/heap_alloc_forloop8a.gno @@ -19,7 +19,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; f := func func() (const-type int){ for i := (const (0 int)); i < (const (1 int)); i++ { x<~VPBlock(2,1)>++ }; return x<~VPBlock(1,1)> }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; f := func func() (const-type int){ for i := (const (0 int)); i < (const (1 int)); i++ { x<~VPBlock(2,1)>++ }; return x<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_forloop8b.gno b/gnovm/tests/files/heap_alloc_forloop8b.gno index 1cd6bf13cc3..7addbf102f5 100644 --- a/gnovm/tests/files/heap_alloc_forloop8b.gno +++ b/gnovm/tests/files/heap_alloc_forloop8b.gno @@ -21,7 +21,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; s := [](const-type int){(const (1 int)), (const (2 int))}; f := func func() (const-type int){ for _, v := range s<~VPBlock(2,1)> { x<~VPBlock(2,2)> += v }; return x<~VPBlock(1,2)> }, x<()~VPBlock(1,1)>>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; s := [](const-type int){(const (1 int)), (const (2 int))}; f := func func() (const-type int){ for _, v := range s<~VPBlock(2,1)> { x<~VPBlock(2,2)> += v }; return x<~VPBlock(1,2)> }, x<()~VPBlock(1,1)>>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 3 diff --git a/gnovm/tests/files/heap_alloc_forloop8c.gno b/gnovm/tests/files/heap_alloc_forloop8c.gno index 11aef1683b8..7337b69a74b 100644 --- a/gnovm/tests/files/heap_alloc_forloop8c.gno +++ b/gnovm/tests/files/heap_alloc_forloop8c.gno @@ -23,7 +23,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; y := (const (1 int)); f := func func() (const-type int){ switch y<~VPBlock(2,1)> { case (const (1 int)): x<~VPBlock(2,2)> += (const (1 int)); default: x<~VPBlock(2,2)> += (const (0 int)) }; return x<~VPBlock(1,2)> }, x<()~VPBlock(1,1)>>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); for i := (const (0 int)); i < (const (2 int)); i++ { x := i; y := (const (1 int)); f := func func() (const-type int){ switch y<~VPBlock(2,1)> { case (const (1 int)): x<~VPBlock(2,2)> += (const (1 int)); default: x<~VPBlock(2,2)> += (const (0 int)) }; return x<~VPBlock(1,2)> }, x<()~VPBlock(1,1)>>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_forloop9.gno b/gnovm/tests/files/heap_alloc_forloop9.gno index 0f1d0b8b23c..da3dfea0749 100644 --- a/gnovm/tests/files/heap_alloc_forloop9.gno +++ b/gnovm/tests/files/heap_alloc_forloop9.gno @@ -28,7 +28,7 @@ func main() { // go 1.22 loop var is not supported for now. // Preprocessed: -// file{ package main; import fmt fmt; func main() { var fns []func(.arg_0 (const-type int)) (const-type int); var recursiveFunc func(.arg_0 (const-type int)) (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { recursiveFunc = func func(num (const-type int)) (const-type int){ x := i<~VPBlock(1,3)>; (const (println func(xs ...interface{})()))((const ("value of x: " string)), x); if num <= (const (0 int)) { return (const (1 int)) }; return num * recursiveFunc(num - (const (1 int))) }>; fns = (const (append func(x []func(.arg_0 int)( int),args ...func(.arg_0 int)( int))(res []func(.arg_0 int)( int))))(fns, recursiveFunc) }; for i, r := range fns { result := r(i); fmt.Printf((const ("Factorial of %d is: %d\n" string)), (const-type gonative{interface {}})(i), (const-type gonative{interface {}})(result)) } } } +// file{ package main; import fmt fmt; func main() { var fns []func(.arg_0 (const-type int)) (const-type int); var recursiveFunc func(.arg_0 (const-type int)) (const-type int); for i := (const (0 int)); i<~VPBlock(1,0)> < (const (3 int)); i<~VPBlock(1,0)>++ { recursiveFunc = func func(num (const-type int)) (const-type int){ x := i<~VPBlock(1,3)>; (const (println func(...interface {})))((const ("value of x: " string)), x); if num <= (const (0 int)) { return (const (1 int)) }; return num * recursiveFunc(num - (const (1 int))) }>; fns = (const (append func([]func(int) int, ...func(int) int) []func(int) int))(fns, recursiveFunc) }; for i, r := range fns { result := r(i); fmt.Printf((const ("Factorial of %d is: %d\n" string)), i, result) } } } // Output: // value of x: 3 diff --git a/gnovm/tests/files/heap_alloc_forloop9_1.gno b/gnovm/tests/files/heap_alloc_forloop9_1.gno index 2576b9b4da6..3fd437d5bc4 100644 --- a/gnovm/tests/files/heap_alloc_forloop9_1.gno +++ b/gnovm/tests/files/heap_alloc_forloop9_1.gno @@ -16,7 +16,7 @@ func main() { } // Preprocessed: -// file{ package main; func Search(n (const-type int), f func(.arg_0 (const-type int)) (const-type bool)) (const-type int) { f((const (1 int))); return (const (0 int)) }; func main() { for x := (const (0 int)); x<~VPBlock(1,0)> < (const (2 int)); x<~VPBlock(1,0)>++ { count := (const (0 int)); (const (println func(xs ...interface{})()))((const (" first: count: " string)), count<~VPBlock(1,1)>); Search((const (1 int)), func func(i (const-type int)) (const-type bool){ count<~VPBlock(1,2)>++; return (const-type bool)(i >= x<~VPBlock(1,3)>) }, x<()~VPBlock(1,0)>>); (const (println func(xs ...interface{})()))((const ("second: count: " string)), count<~VPBlock(1,1)>) } } } +// file{ package main; func Search(n (const-type int), f func(.arg_0 (const-type int)) (const-type bool)) (const-type int) { f((const (1 int))); return (const (0 int)) }; func main() { for x := (const (0 int)); x<~VPBlock(1,0)> < (const (2 int)); x<~VPBlock(1,0)>++ { count := (const (0 int)); (const (println func(...interface {})))((const (" first: count: " string)), count<~VPBlock(1,1)>); Search((const (1 int)), func func(i (const-type int)) (const-type bool){ count<~VPBlock(1,2)>++; return (const-type bool)(i >= x<~VPBlock(1,3)>) }, x<()~VPBlock(1,0)>>); (const (println func(...interface {})))((const ("second: count: " string)), count<~VPBlock(1,1)>) } } } // Output: // first: count: 0 diff --git a/gnovm/tests/files/heap_alloc_forloop9_2.gno b/gnovm/tests/files/heap_alloc_forloop9_2.gno index 6b1ebdbb7e9..6c731500e22 100644 --- a/gnovm/tests/files/heap_alloc_forloop9_2.gno +++ b/gnovm/tests/files/heap_alloc_forloop9_2.gno @@ -23,7 +23,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); (const (println func(xs ...interface{})()))((const ("start for loop" string))); for i := (const (0 int)); i < (const (2 int)); i++ { defer func func(){ (const (println func(xs ...interface{})()))((const ("defer" string))); for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } }(); x := i; f := func func() (const-type int){ return x<~VPBlock(1,1)> }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; (const (println func(xs ...interface{})()))((const ("end for loop" string))) } } +// file{ package main; func main() { var fns []func() (const-type int); (const (println func(...interface {})))((const ("start for loop" string))); for i := (const (0 int)); i < (const (2 int)); i++ { defer func func(){ (const (println func(...interface {})))((const ("defer" string))); for _, fn := range fns { (const (println func(...interface {})))(fn()) } }(); x := i; f := func func() (const-type int){ return x<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; (const (println func(...interface {})))((const ("end for loop" string))) } } // Output: // start for loop diff --git a/gnovm/tests/files/heap_alloc_forloop9b.gno b/gnovm/tests/files/heap_alloc_forloop9b.gno index 03e84fcd2b2..d58624dab3a 100644 --- a/gnovm/tests/files/heap_alloc_forloop9b.gno +++ b/gnovm/tests/files/heap_alloc_forloop9b.gno @@ -19,7 +19,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var y (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); for i := (const (0 int)); i < (const (2 int)); i++ { for j := (const (0 int)); j < (const (2 int)); j++ { x := y; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); y++ } } } } +// file{ package main; func main() { var y (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); for i := (const (0 int)); i < (const (2 int)); i++ { for j := (const (0 int)); j < (const (2 int)); j++ { x := y; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); y++ } } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop0.gno b/gnovm/tests/files/heap_alloc_gotoloop0.gno index d13baedf7c4..d9b04756c0b 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop0.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop0.gno @@ -21,7 +21,7 @@ LABEL_1: } // Preprocessed: -// file{ package main; func main() { var counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); if counter == (const (2 int)) { return }; x := counter; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); counter++; goto LABEL_1<0,3> } } +// file{ package main; func main() { var counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); if counter == (const (2 int)) { return }; x := counter; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); counter++; goto LABEL_1<0,3> } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop1.gno b/gnovm/tests/files/heap_alloc_gotoloop1.gno index ea26952e0a4..40637c7ae7c 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop1.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop1.gno @@ -15,7 +15,7 @@ loop: // because it is not actually used in a &ref or closure context. // Preprocessed: -// file{ package main; func main() { c := (const (0 int)); i := (const (1 int)); (const (println func(xs ...interface{})()))(i); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,1> } } } +// file{ package main; func main() { c := (const (0 int)); i := (const (1 int)); (const (println func(...interface {})))(i); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,1> } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_gotoloop2.gno b/gnovm/tests/files/heap_alloc_gotoloop2.gno index e7b2917a8ff..db904e58ecc 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop2.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop2.gno @@ -22,7 +22,7 @@ loop: // You can tell by the preprocess printout of i and i<()~...>. // Preprocessed: -// file{ package main; func main() { c := (const (0 int)); closures := []func(){}; i := c; closures = (const (append func(x []func()(),args ...func()())(res []func()())))(closures, func func(){ (const (println func(xs ...interface{})()))(i<~VPBlock(1,0)>) }>); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,2> }; for _, cl := range closures { cl() } } } +// file{ package main; func main() { c := (const (0 int)); closures := []func(){}; i := c; closures = (const (append func([]func(), ...func()) []func()))(closures, func func(){ (const (println func(...interface {})))(i<~VPBlock(1,0)>) }>); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,2> }; for _, cl := range closures { cl() } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop3.gno b/gnovm/tests/files/heap_alloc_gotoloop3.gno index a248e8aa0bc..6fc64db161e 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop3.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop3.gno @@ -19,7 +19,7 @@ loop: // You can tell by the preprocess printout of i and i<~...>. // Preprocessed: -// file{ package main; func main() { c := (const (0 int)); refs := []*((const-type int)){}; i := c; refs = (const (append func(x []*int,args ...*int)(res []*int)))(refs, &(i<~VPBlock(1,2)>)); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,2> }; for _, ref := range refs { (const (println func(xs ...interface{})()))(*(ref)) } } } +// file{ package main; func main() { c := (const (0 int)); refs := []*((const-type int)){}; i := c; refs = (const (append func([]*int, ...*int) []*int))(refs, &(i<~VPBlock(1,2)>)); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,2> }; for _, ref := range refs { (const (println func(...interface {})))(*(ref)) } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop4.gno b/gnovm/tests/files/heap_alloc_gotoloop4.gno index 062d832ac16..e626bef61cd 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop4.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop4.gno @@ -19,7 +19,7 @@ loop: // You can tell by the preprocess printout of i and i<~...>. // Preprocessed: -// file{ package main; func main() { c := (const (0 int)); refs := []*((const-type int)){}; var i (const-type int) = c; refs = (const (append func(x []*int,args ...*int)(res []*int)))(refs, &(i<~VPBlock(1,2)>)); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,2> }; for _, ref := range refs { (const (println func(xs ...interface{})()))(*(ref)) } } } +// file{ package main; func main() { c := (const (0 int)); refs := []*((const-type int)){}; var i (const-type int) = c; refs = (const (append func([]*int, ...*int) []*int))(refs, &(i<~VPBlock(1,2)>)); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,2> }; for _, ref := range refs { (const (println func(...interface {})))(*(ref)) } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop5.gno b/gnovm/tests/files/heap_alloc_gotoloop5.gno index fb7a82228f1..c9b8b2d859d 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop5.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop5.gno @@ -24,7 +24,7 @@ loop: // You can tell by the preprocess printout of i and i<~...>. // Preprocessed: -// file{ package main; func main() { c := (const (0 int)); refs := []*((const-type int)){}; i, j := c, (const (2 int)); refs = (const (append func(x []*int,args ...*int)(res []*int)))(refs, &(i<~VPBlock(1,2)>)); i<~VPBlock(1,2)> += (const (1 int)); j += (const (1 int)); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,2> }; for _, ref := range refs { (const (println func(xs ...interface{})()))(*(ref)) }; if (const (false bool)) { (const (println func(xs ...interface{})()))(j) } } } +// file{ package main; func main() { c := (const (0 int)); refs := []*((const-type int)){}; i, j := c, (const (2 int)); refs = (const (append func([]*int, ...*int) []*int))(refs, &(i<~VPBlock(1,2)>)); i<~VPBlock(1,2)> += (const (1 int)); j += (const (1 int)); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,2> }; for _, ref := range refs { (const (println func(...interface {})))(*(ref)) }; if (const (false bool)) { (const (println func(...interface {})))(j) } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_gotoloop6.gno b/gnovm/tests/files/heap_alloc_gotoloop6.gno index d2efbb85df2..44410a3914e 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop6.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop6.gno @@ -24,7 +24,7 @@ loop: // You can tell by the preprocess printout of i and i<~...>. // Preprocessed: -// file{ package main; func main() { c := (const (0 int)); refs := []*((const-type int)){}; var i, j (const-type int) = c, (const (2 int)); refs = (const (append func(x []*int,args ...*int)(res []*int)))(refs, &(i<~VPBlock(1,2)>)); i<~VPBlock(1,2)> += (const (1 int)); j += (const (1 int)); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,2> }; for _, ref := range refs { (const (println func(xs ...interface{})()))(*(ref)) }; if (const (false bool)) { (const (println func(xs ...interface{})()))(j) } } } +// file{ package main; func main() { c := (const (0 int)); refs := []*((const-type int)){}; var i, j (const-type int) = c, (const (2 int)); refs = (const (append func([]*int, ...*int) []*int))(refs, &(i<~VPBlock(1,2)>)); i<~VPBlock(1,2)> += (const (1 int)); j += (const (1 int)); c += (const (1 int)); if c < (const (10 int)) { goto loop<1,2> }; for _, ref := range refs { (const (println func(...interface {})))(*(ref)) }; if (const (false bool)) { (const (println func(...interface {})))(j) } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_gotoloop7.gno b/gnovm/tests/files/heap_alloc_gotoloop7.gno index 457fdb74a01..90f179cc061 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop7.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop7.gno @@ -33,7 +33,7 @@ loop: // You can tell by the preprocess printout of i and i<~...>. // Preprocessed: -// file{ package main; func main() { c := (const (0 int)); refs := []*((const-type int)){}; var i, j (const-type int) = c, (const (2 int)); refs = (const (append func(x []*int,args ...*int)(res []*int)))(refs, &(i<~VPBlock(1,2)>)); i<~VPBlock(1,2)> += (const (1 int)); j += (const (1 int)); c += (const (1 int)); thing := func func(){ i := (const (2 int)); j := (const (3 int)); (const (println func(xs ...interface{})()))(i); (const (println func(xs ...interface{})()))(j) }; if c < (const (10 int)) { goto loop<1,2> }; for _, ref := range refs { (const (println func(xs ...interface{})()))(*(ref)) }; if (const (false bool)) { (const (println func(xs ...interface{})()))(j) }; if (const (false bool)) { thing() } } } +// file{ package main; func main() { c := (const (0 int)); refs := []*((const-type int)){}; var i, j (const-type int) = c, (const (2 int)); refs = (const (append func([]*int, ...*int) []*int))(refs, &(i<~VPBlock(1,2)>)); i<~VPBlock(1,2)> += (const (1 int)); j += (const (1 int)); c += (const (1 int)); thing := func func(){ i := (const (2 int)); j := (const (3 int)); (const (println func(...interface {})))(i); (const (println func(...interface {})))(j) }; if c < (const (10 int)) { goto loop<1,2> }; for _, ref := range refs { (const (println func(...interface {})))(*(ref)) }; if (const (false bool)) { (const (println func(...interface {})))(j) }; if (const (false bool)) { thing() } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_gotoloop8.gno b/gnovm/tests/files/heap_alloc_gotoloop8.gno index f3421048d41..cbfc1b7dce1 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop8.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop8.gno @@ -22,7 +22,7 @@ loop2: // This one doesn't because the goto stmt doesn't go back far enough. // Preprocessed: -// file{ package main; func main() { c := (const (0 int)); closures := []func(){}; goto loop1<0,3>; i := (const (1 int)); closures = (const (append func(x []func()(),args ...func()())(res []func()())))(closures, func func(){ (const (println func(xs ...interface{})()))(i) }); c += (const (1 int)); if c < (const (10 int)) { goto loop2<1,4> }; for _, cl := range closures { cl() } } } +// file{ package main; func main() { c := (const (0 int)); closures := []func(){}; goto loop1<0,3>; i := (const (1 int)); closures = (const (append func([]func(), ...func()) []func()))(closures, func func(){ (const (println func(...interface {})))(i) }); c += (const (1 int)); if c < (const (10 int)) { goto loop2<1,4> }; for _, cl := range closures { cl() } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9.gno b/gnovm/tests/files/heap_alloc_gotoloop9.gno index 38204652216..d5ff671cabe 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9.gno @@ -20,7 +20,7 @@ loop2: // This one doesn't because the goto stmt doesn't go back far enough. // Preprocessed: -// file{ package main; func main() { c := (const (0 int)); closures := []func(){}; i := (const (1 int)); closures = (const (append func(x []func()(),args ...func()())(res []func()())))(closures, func func(){ (const (println func(xs ...interface{})()))(i) }); c += (const (1 int)); if c < (const (10 int)) { goto loop2<1,3> }; for _, cl := range closures { cl() } } } +// file{ package main; func main() { c := (const (0 int)); closures := []func(){}; i := (const (1 int)); closures = (const (append func([]func(), ...func()) []func()))(closures, func func(){ (const (println func(...interface {})))(i) }); c += (const (1 int)); if c < (const (10 int)) { goto loop2<1,3> }; for _, cl := range closures { cl() } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_10.gno b/gnovm/tests/files/heap_alloc_gotoloop9_10.gno index bb8a2bad3ef..99ed4ded2b9 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_10.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_10.gno @@ -41,7 +41,7 @@ LOOP_2: } // Preprocessed: -// file{ package main; import fmt fmt; var s1 []*((const-type int)); var s2 []*((const-type int)); func main() { defer func func(){ for i, v := range s1 { fmt.Printf((const ("s1[%d] is %d\n" string)), (const-type gonative{interface {}})(i), (const-type gonative{interface {}})(*(v))) }; for i, v := range s2 { fmt.Printf((const ("s2[%d] is %d\n" string)), (const-type gonative{interface {}})(i), (const-type gonative{interface {}})(*(v))) } }(); var c1, c2 (const-type int); x := c1; s1 = (const (append func(x []*int,args ...*int)(res []*int)))(s1, &(x<~VPBlock(1,2)>)); (const (println func(xs ...interface{})()))((const ("loop_1" string)), c1); c1++; y := c2; s2 = (const (append func(x []*int,args ...*int)(res []*int)))(s2, &(y<~VPBlock(1,3)>)); (const (println func(xs ...interface{})()))((const ("loop_2" string)), c2); c2++; if c1 < (const (3 int)) { goto LOOP_1<1,2> }; if c2 < (const (6 int)) { goto LOOP_2<1,6> } } } +// file{ package main; import fmt fmt; var s1 []*((const-type int)); var s2 []*((const-type int)); func main() { defer func func(){ for i, v := range s1 { fmt.Printf((const ("s1[%d] is %d\n" string)), i, *(v)) }; for i, v := range s2 { fmt.Printf((const ("s2[%d] is %d\n" string)), i, *(v)) } }(); var c1, c2 (const-type int); x := c1; s1 = (const (append func([]*int, ...*int) []*int))(s1, &(x<~VPBlock(1,2)>)); (const (println func(...interface {})))((const ("loop_1" string)), c1); c1++; y := c2; s2 = (const (append func([]*int, ...*int) []*int))(s2, &(y<~VPBlock(1,3)>)); (const (println func(...interface {})))((const ("loop_2" string)), c2); c2++; if c1 < (const (3 int)) { goto LOOP_1<1,2> }; if c2 < (const (6 int)) { goto LOOP_2<1,6> } } } // Output: // loop_1 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_11.gno b/gnovm/tests/files/heap_alloc_gotoloop9_11.gno index 839612704a8..46a35663c22 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_11.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_11.gno @@ -22,7 +22,7 @@ LABEL_1: } // Preprocessed: -// file{ package main; func main() { var y, counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); if counter == (const (2 int)) { return }; var _, x = (const (0 int)), y; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,3> } } +// file{ package main; func main() { var y, counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); if counter == (const (2 int)) { return }; var _, x = (const (0 int)), y; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,3> } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_12.gno b/gnovm/tests/files/heap_alloc_gotoloop9_12.gno index 4d024a58673..c3e4de05cca 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_12.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_12.gno @@ -41,7 +41,7 @@ LOOP_START: } // Preprocessed: -// file{ package main; import fmt fmt; func main() { counter0 := (const (0 int)); counter1 := (const (0 int)); y := (const (0 int)); var fs []func(); defer func func(){ for _, ff := range fs { ff() } }(); if counter0 < (const (2 int)) { counter1 = (const (0 int)); fmt.Printf((const ("Outer loop start: counter0=%d\n" string)), (const-type gonative{interface {}})(counter0)); if counter1 < (const (2 int)) { fmt.Printf((const (" Nested loop: counter1=%d\n" string)), (const-type gonative{interface {}})(counter1)); counter1++; goto NESTED_LOOP_START<1,2> }; x := y; fs = (const (append func(x []func()(),args ...func()())(res []func()())))(fs, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); fmt.Println((const ("Exiting nested loop" string))); counter0++; y++; goto LOOP_START<1,5> } else { return } } } +// file{ package main; import fmt fmt; func main() { counter0 := (const (0 int)); counter1 := (const (0 int)); y := (const (0 int)); var fs []func(); defer func func(){ for _, ff := range fs { ff() } }(); if counter0 < (const (2 int)) { counter1 = (const (0 int)); fmt.Printf((const ("Outer loop start: counter0=%d\n" string)), counter0); if counter1 < (const (2 int)) { fmt.Printf((const (" Nested loop: counter1=%d\n" string)), counter1); counter1++; goto NESTED_LOOP_START<1,2> }; x := y; fs = (const (append func([]func(), ...func()) []func()))(fs, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); fmt.Println((const ("Exiting nested loop" string))); counter0++; y++; goto LOOP_START<1,5> } else { return } } } // Output: // Outer loop start: counter0=0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_13.gno b/gnovm/tests/files/heap_alloc_gotoloop9_13.gno index 7c7b6777223..0d2d9445b02 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_13.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_13.gno @@ -32,7 +32,7 @@ LABEL_2: } // Preprocessed: -// file{ package main; func main() { var y, counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); x := y; if counter == (const (2 int)) { counter = (const (0 int)); goto LABEL_2<1,9> }; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,3>; if counter == (const (2 int)) { return }; z := y; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(z<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_2<0,9> } } +// file{ package main; func main() { var y, counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); x := y; if counter == (const (2 int)) { counter = (const (0 int)); goto LABEL_2<1,9> }; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,3>; if counter == (const (2 int)) { return }; z := y; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(z<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_2<0,9> } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_14.gno b/gnovm/tests/files/heap_alloc_gotoloop9_14.gno index f92b31eb7de..db17db46477 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_14.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_14.gno @@ -34,7 +34,7 @@ LABEL_2: } // Preprocessed: -// file{ package main; func main() { var y, counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); { x := y; if counter == (const (2 int)) { counter = (const (0 int)); goto LABEL_2<2,4> }; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,0> }; if counter == (const (2 int)) { return }; z := y; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(z<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_2<0,4> } } +// file{ package main; func main() { var y, counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); { x := y; if counter == (const (2 int)) { counter = (const (0 int)); goto LABEL_2<2,4> }; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,0> }; if counter == (const (2 int)) { return }; z := y; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(z<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_2<0,4> } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_15.gno b/gnovm/tests/files/heap_alloc_gotoloop9_15.gno index d774af55eb6..b4df1c1c5e0 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_15.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_15.gno @@ -37,7 +37,7 @@ LABEL_2: } // Preprocessed: -// file{ package main; var y, counter (const-type int); var f []func(); func main() { defer func func(){ for _, ff := range f { ff() } }(); x := y; if counter == (const (2 int)) { counter = (const (0 int)); bar(); return }; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,1> }; func bar() { (const (println func(xs ...interface{})()))((const ("---bar---" string))); if counter == (const (2 int)) { (const (println func(xs ...interface{})()))((const ("---end---" string))); return }; z := y; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(z<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_2<0,1> } } +// file{ package main; var y, counter (const-type int); var f []func(); func main() { defer func func(){ for _, ff := range f { ff() } }(); x := y; if counter == (const (2 int)) { counter = (const (0 int)); bar(); return }; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,1> }; func bar() { (const (println func(...interface {})))((const ("---bar---" string))); if counter == (const (2 int)) { (const (println func(...interface {})))((const ("---end---" string))); return }; z := y; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(z<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_2<0,1> } } // Output: // ---bar--- diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_15a.gno b/gnovm/tests/files/heap_alloc_gotoloop9_15a.gno index 2d9de02ecc3..ff5cfc99f8d 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_15a.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_15a.gno @@ -35,7 +35,7 @@ LABEL_2: } // Preprocessed: -// file{ package main; var y, counter (const-type int); var f []func(); func main() { x := y; if counter == (const (2 int)) { counter = (const (0 int)); bar(); for _, ff := range f { ff() }; return }; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,0> }; func bar() { (const (println func(xs ...interface{})()))((const ("---bar---" string))); if counter == (const (2 int)) { (const (println func(xs ...interface{})()))((const ("---end---" string))); return }; z := y; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(z<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_2<0,1> } } +// file{ package main; var y, counter (const-type int); var f []func(); func main() { x := y; if counter == (const (2 int)) { counter = (const (0 int)); bar(); for _, ff := range f { ff() }; return }; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,0> }; func bar() { (const (println func(...interface {})))((const ("---bar---" string))); if counter == (const (2 int)) { (const (println func(...interface {})))((const ("---end---" string))); return }; z := y; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(z<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_2<0,1> } } // Output: // ---bar--- diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_16.gno b/gnovm/tests/files/heap_alloc_gotoloop9_16.gno index 8971b04aee6..6ec3e814af7 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_16.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_16.gno @@ -41,7 +41,7 @@ LOOP_START: } // Preprocessed: -// file{ package main; import fmt fmt; func main() { counter0 := (const (0 int)); counter1 := (const (0 int)); y := (const (0 int)); var fs []func(); defer func func(){ for _, ff := range fs { ff() } }(); if counter0 < (const (2 int)) { x := y; counter1 = (const (0 int)); fmt.Printf((const ("Outer loop start: counter0=%d\n" string)), (const-type gonative{interface {}})(counter0)); if counter1 < (const (2 int)) { fmt.Printf((const (" Nested loop: counter1=%d\n" string)), (const-type gonative{interface {}})(counter1)); fs = (const (append func(x []func()(),args ...func()())(res []func()())))(fs, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); counter1++; goto NESTED_LOOP_START<1,3> }; fmt.Println((const ("Exiting nested loop" string))); counter0++; y++; goto LOOP_START<1,5> } else { return } } } +// file{ package main; import fmt fmt; func main() { counter0 := (const (0 int)); counter1 := (const (0 int)); y := (const (0 int)); var fs []func(); defer func func(){ for _, ff := range fs { ff() } }(); if counter0 < (const (2 int)) { x := y; counter1 = (const (0 int)); fmt.Printf((const ("Outer loop start: counter0=%d\n" string)), counter0); if counter1 < (const (2 int)) { fmt.Printf((const (" Nested loop: counter1=%d\n" string)), counter1); fs = (const (append func([]func(), ...func()) []func()))(fs, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); counter1++; goto NESTED_LOOP_START<1,3> }; fmt.Println((const ("Exiting nested loop" string))); counter0++; y++; goto LOOP_START<1,5> } else { return } } } // Output: // Outer loop start: counter0=0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_17.gno b/gnovm/tests/files/heap_alloc_gotoloop9_17.gno index 9e5073c1bfe..1e876973a8f 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_17.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_17.gno @@ -41,7 +41,7 @@ LOOP_START: } // Preprocessed: -// file{ package main; import fmt fmt; func main() { counter0 := (const (0 int)); counter1 := (const (0 int)); y := (const (0 int)); var fs []func(); defer func func(){ for _, ff := range fs { ff() } }(); x := y; if counter0 < (const (2 int)) { counter1 = (const (0 int)); fmt.Printf((const ("Outer loop start: counter0=%d\n" string)), (const-type gonative{interface {}})(counter0)); if counter1 < (const (2 int)) { fmt.Printf((const (" Nested loop: counter1=%d\n" string)), (const-type gonative{interface {}})(counter1)); fs = (const (append func(x []func()(),args ...func()())(res []func()())))(fs, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); counter1++; goto NESTED_LOOP_START<1,2> }; fmt.Println((const ("Exiting nested loop" string))); counter0++; y++; goto LOOP_START<1,5> } else { return } } } +// file{ package main; import fmt fmt; func main() { counter0 := (const (0 int)); counter1 := (const (0 int)); y := (const (0 int)); var fs []func(); defer func func(){ for _, ff := range fs { ff() } }(); x := y; if counter0 < (const (2 int)) { counter1 = (const (0 int)); fmt.Printf((const ("Outer loop start: counter0=%d\n" string)), counter0); if counter1 < (const (2 int)) { fmt.Printf((const (" Nested loop: counter1=%d\n" string)), counter1); fs = (const (append func([]func(), ...func()) []func()))(fs, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); counter1++; goto NESTED_LOOP_START<1,2> }; fmt.Println((const ("Exiting nested loop" string))); counter0++; y++; goto LOOP_START<1,5> } else { return } } } // Output: // Outer loop start: counter0=0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_18.gno b/gnovm/tests/files/heap_alloc_gotoloop9_18.gno index 1578eb5897f..be6fe21bcad 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_18.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_18.gno @@ -23,7 +23,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var y, counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); { if counter == (const (2 int)) { return }; x := y; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,0> } } } +// file{ package main; func main() { var y, counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); { if counter == (const (2 int)) { return }; x := y; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,0> } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_19.gno b/gnovm/tests/files/heap_alloc_gotoloop9_19.gno index fb1b995372e..fdcd1fc6074 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_19.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_19.gno @@ -23,7 +23,7 @@ LABEL_1: } // Preprocessed: -// file{ package main; func main() { var y, counter (const-type int); var f []func() func() (const-type int); defer func func(){ for _, ff := range f { (const (println func(xs ...interface{})()))(ff()()) } }(); if counter == (const (2 int)) { return }; x := y; f = (const (append func(x []func()( func()( int)),args ...func()( func()( int)))(res []func()( func()( int)))))(f, func func() func() (const-type int){ return func func() (const-type int){ return x<~VPBlock(2,1)> } }>); y++; counter++; goto LABEL_1<0,3> } } +// file{ package main; func main() { var y, counter (const-type int); var f []func() func() (const-type int); defer func func(){ for _, ff := range f { (const (println func(...interface {})))(ff()()) } }(); if counter == (const (2 int)) { return }; x := y; f = (const (append func([]func() func() int, ...func() func() int) []func() func() int))(f, func func() func() (const-type int){ return func func() (const-type int){ return x<~VPBlock(2,1)> } }>); y++; counter++; goto LABEL_1<0,3> } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_20.gno b/gnovm/tests/files/heap_alloc_gotoloop9_20.gno index 04896b425a5..22013f57a45 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_20.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_20.gno @@ -25,7 +25,7 @@ LABEL_1: } // Preprocessed: -// file{ package main; func main() { var y, counter (const-type int); var f []func() (const-type int), func() (const-type int); defer func func(){ for _, ff := range f { n, f := ff(); (const (println func(xs ...interface{})()))(n + f()) } }(); if counter == (const (2 int)) { return }; x := y; z := y; f = (const (append func(x []func()( int, func()( int)),args ...func()( int, func()( int)))(res []func()( int, func()( int)))))(f, func func() (const-type int), func() (const-type int){ return z<~VPBlock(1,2)>, func func() (const-type int){ return x<~VPBlock(2,3)> } }, x<()~VPBlock(1,3)>>); y++; counter++; goto LABEL_1<0,3> } } +// file{ package main; func main() { var y, counter (const-type int); var f []func() (const-type int), func() (const-type int); defer func func(){ for _, ff := range f { n, f := ff(); (const (println func(...interface {})))(n + f()) } }(); if counter == (const (2 int)) { return }; x := y; z := y; f = (const (append func([]func() (int, func() int), ...func() (int, func() int)) []func() (int, func() int)))(f, func func() (const-type int), func() (const-type int){ return z<~VPBlock(1,2)>, func func() (const-type int){ return x<~VPBlock(2,3)> } }, x<()~VPBlock(1,3)>>); y++; counter++; goto LABEL_1<0,3> } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_21.gno b/gnovm/tests/files/heap_alloc_gotoloop9_21.gno index fa27f3376e0..99852abf6df 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_21.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_21.gno @@ -24,7 +24,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); f1 := func func(){ if counter == (const (2 int)) { return }; x := counter; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); counter++; goto LABEL_1<0,0> }; f1() } } +// file{ package main; func main() { var counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); f1 := func func(){ if counter == (const (2 int)) { return }; x := counter; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); counter++; goto LABEL_1<0,0> }; f1() } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_21a.gno b/gnovm/tests/files/heap_alloc_gotoloop9_21a.gno index 48364ed4075..58c7ba421dc 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_21a.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_21a.gno @@ -26,7 +26,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); f1 := func func(){ if counter == (const (2 int)) { return }; x := counter; func func(){ f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(2,0)>) }) }>(); counter++; goto LABEL_1<0,0> }; f1() } } +// file{ package main; func main() { var counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); f1 := func func(){ if counter == (const (2 int)) { return }; x := counter; func func(){ f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(2,0)>) }) }>(); counter++; goto LABEL_1<0,0> }; f1() } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_21b.gno b/gnovm/tests/files/heap_alloc_gotoloop9_21b.gno index 8758608a5e6..c5c3cf6f5bf 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_21b.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_21b.gno @@ -27,7 +27,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); f1 := func func(){ if counter == (const (2 int)) { return }; func func(){ x := counter; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x) }) }(); counter++; goto LABEL_1<0,0> }; f1() } } +// file{ package main; func main() { var counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); f1 := func func(){ if counter == (const (2 int)) { return }; func func(){ x := counter; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x) }) }(); counter++; goto LABEL_1<0,0> }; f1() } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_gotoloop9_22.gno b/gnovm/tests/files/heap_alloc_gotoloop9_22.gno index cd283345869..ee55056f681 100644 --- a/gnovm/tests/files/heap_alloc_gotoloop9_22.gno +++ b/gnovm/tests/files/heap_alloc_gotoloop9_22.gno @@ -24,7 +24,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var y, counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); for i := (const (0 int)); i < (const (2 int)); i++ { counter = (const (0 int)); if counter == (const (2 int)) { continue }; x := y; f = (const (append func(x []func()(),args ...func()())(res []func()())))(f, func func(){ (const (println func(xs ...interface{})()))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,1> } } } +// file{ package main; func main() { var y, counter (const-type int); var f []func(); defer func func(){ for _, ff := range f { ff() } }(); for i := (const (0 int)); i < (const (2 int)); i++ { counter = (const (0 int)); if counter == (const (2 int)) { continue }; x := y; f = (const (append func([]func(), ...func()) []func()))(f, func func(){ (const (println func(...interface {})))(x<~VPBlock(1,0)>) }>); y++; counter++; goto LABEL_1<0,1> } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_range1.gno b/gnovm/tests/files/heap_alloc_range1.gno index 34520729a06..7623e132866 100644 --- a/gnovm/tests/files/heap_alloc_range1.gno +++ b/gnovm/tests/files/heap_alloc_range1.gno @@ -22,7 +22,7 @@ func main() { } // Preprocessed: -// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), (const-type gonative{interface {}})(i), (const-type gonative{interface {}})(*(e))) } }(); s := [](const-type int){(const (0 int)), (const (1 int)), (const (2 int))}; for i, _ := range s { s1 = (const (append func(x []*int,args ...*int)(res []*int)))(s1, &(i)) } }; func main() { forLoopRef() } } +// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); s := [](const-type int){(const (0 int)), (const (1 int)), (const (2 int))}; for i, _ := range s { s1 = (const (append func([]*int, ...*int) []*int))(s1, &(i)) } }; func main() { forLoopRef() } } // Output: // s1[0] is: 2 diff --git a/gnovm/tests/files/heap_alloc_range2.gno b/gnovm/tests/files/heap_alloc_range2.gno index 34b7cc527af..0f39b45ee2a 100644 --- a/gnovm/tests/files/heap_alloc_range2.gno +++ b/gnovm/tests/files/heap_alloc_range2.gno @@ -22,7 +22,7 @@ func main() { } // Preprocessed: -// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), (const-type gonative{interface {}})(i), (const-type gonative{interface {}})(*(e))) } }(); s := [](const-type int){(const (0 int)), (const (1 int)), (const (2 int))}; for _, v := range s { s1 = (const (append func(x []*int,args ...*int)(res []*int)))(s1, &(v)) } }; func main() { forLoopRef() } } +// file{ package main; import fmt fmt; var s1 []*((const-type int)); func forLoopRef() { defer func func(){ for i, e := range s1 { fmt.Printf((const ("s1[%d] is: %d\n" string)), i, *(e)) } }(); s := [](const-type int){(const (0 int)), (const (1 int)), (const (2 int))}; for _, v := range s { s1 = (const (append func([]*int, ...*int) []*int))(s1, &(v)) } }; func main() { forLoopRef() } } // Output: // s1[0] is: 2 diff --git a/gnovm/tests/files/heap_alloc_range3.gno b/gnovm/tests/files/heap_alloc_range3.gno index 032d0cf8b6d..e10a82fbedd 100644 --- a/gnovm/tests/files/heap_alloc_range3.gno +++ b/gnovm/tests/files/heap_alloc_range3.gno @@ -14,7 +14,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { s := [](const-type int){(const (1 int)), (const (2 int))}; f := func func(){ for i, v := range s { (const (println func(xs ...interface{})()))(i); (const (println func(xs ...interface{})()))(v) } }; f() } } +// file{ package main; func main() { s := [](const-type int){(const (1 int)), (const (2 int))}; f := func func(){ for i, v := range s { (const (println func(...interface {})))(i); (const (println func(...interface {})))(v) } }; f() } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_range4.gno b/gnovm/tests/files/heap_alloc_range4.gno index 2418184caca..7a8dffbb091 100644 --- a/gnovm/tests/files/heap_alloc_range4.gno +++ b/gnovm/tests/files/heap_alloc_range4.gno @@ -16,7 +16,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); s := [](const-type int){(const (1 int)), (const (2 int)), (const (3 int))}; for i, _ := range s { x := i; f := func func() (const-type int){ return x<~VPBlock(1,1)> }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); s := [](const-type int){(const (1 int)), (const (2 int)), (const (3 int))}; for i, _ := range s { x := i; f := func func() (const-type int){ return x<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_range4a.gno b/gnovm/tests/files/heap_alloc_range4a.gno index 229d59d6011..7f921ffe093 100644 --- a/gnovm/tests/files/heap_alloc_range4a.gno +++ b/gnovm/tests/files/heap_alloc_range4a.gno @@ -19,7 +19,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); m := map[(const-type string)] (const-type int){(const ("a" string)): (const (1 int)), (const ("b" string)): (const (2 int))}; for _, v := range m { x := v; f := func func() (const-type int){ if (const (true bool)) { return x<~VPBlock(2,1)> }; return (const (0 int)) }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); m := map[(const-type string)] (const-type int){(const ("a" string)): (const (1 int)), (const ("b" string)): (const (2 int))}; for _, v := range m { x := v; f := func func() (const-type int){ if (const (true bool)) { return x<~VPBlock(2,1)> }; return (const (0 int)) }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_range4a1.gno b/gnovm/tests/files/heap_alloc_range4a1.gno index 7b126b0e530..4f70ca616f2 100644 --- a/gnovm/tests/files/heap_alloc_range4a1.gno +++ b/gnovm/tests/files/heap_alloc_range4a1.gno @@ -15,7 +15,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); m := map[(const-type string)] (const-type int){(const ("a" string)): (const (1 int)), (const ("b" string)): (const (2 int))}; for _, v := range m { f := func func() (const-type int){ return v }; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); m := map[(const-type string)] (const-type int){(const ("a" string)): (const (1 int)), (const ("b" string)): (const (2 int))}; for _, v := range m { f := func func() (const-type int){ return v }; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 2 diff --git a/gnovm/tests/files/heap_alloc_range4a2.gno b/gnovm/tests/files/heap_alloc_range4a2.gno index c54450f4619..a554b024d62 100644 --- a/gnovm/tests/files/heap_alloc_range4a2.gno +++ b/gnovm/tests/files/heap_alloc_range4a2.gno @@ -25,7 +25,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); y := (const (0 int)); m := map[(const-type string)] (const-type int){(const ("a" string)): (const (1 int)), (const ("b" string)): (const (2 int))}; for _, v := range m { x := v; f := func func() (const-type int){ switch y { case (const (0 int)): if (const (true bool)) { return x<~VPBlock(3,1)> }; default: return (const (0 int)) }; return (const (0 int)) }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); y := (const (0 int)); m := map[(const-type string)] (const-type int){(const ("a" string)): (const (1 int)), (const ("b" string)): (const (2 int))}; for _, v := range m { x := v; f := func func() (const-type int){ switch y { case (const (0 int)): if (const (true bool)) { return x<~VPBlock(3,1)> }; default: return (const (0 int)) }; return (const (0 int)) }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 1 diff --git a/gnovm/tests/files/heap_alloc_range4b.gno b/gnovm/tests/files/heap_alloc_range4b.gno index b4a380b361b..7c66bdfcce6 100644 --- a/gnovm/tests/files/heap_alloc_range4b.gno +++ b/gnovm/tests/files/heap_alloc_range4b.gno @@ -16,7 +16,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); s := (const ("hello" string)); for i, _ := range s { x := i; f := func func() (const-type int){ return x<~VPBlock(1,1)> }>; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); s := (const ("hello" string)); for i, _ := range s { x := i; f := func func() (const-type int){ return x<~VPBlock(1,1)> }>; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 0 diff --git a/gnovm/tests/files/heap_alloc_range4b1.gno b/gnovm/tests/files/heap_alloc_range4b1.gno index 24dd99ea98f..523c9d24935 100644 --- a/gnovm/tests/files/heap_alloc_range4b1.gno +++ b/gnovm/tests/files/heap_alloc_range4b1.gno @@ -15,7 +15,7 @@ func main() { } // Preprocessed: -// file{ package main; func main() { var fns []func() (const-type int); s := (const ("hello" string)); for i, _ := range s { f := func func() (const-type int){ return i }; fns = (const (append func(x []func()( int),args ...func()( int))(res []func()( int))))(fns, f) }; for _, fn := range fns { (const (println func(xs ...interface{})()))(fn()) } } } +// file{ package main; func main() { var fns []func() (const-type int); s := (const ("hello" string)); for i, _ := range s { f := func func() (const-type int){ return i }; fns = (const (append func([]func() int, ...func() int) []func() int))(fns, f) }; for _, fn := range fns { (const (println func(...interface {})))(fn()) } } } // Output: // 4 diff --git a/gnovm/tests/files/import12.gno b/gnovm/tests/files/import12.gno new file mode 100644 index 00000000000..a3f3fe4fbf6 --- /dev/null +++ b/gnovm/tests/files/import12.gno @@ -0,0 +1,14 @@ +// PKGPATH: gno.land/p/demo + +package demo + +import ( + "internal/bytealg" +) + +func main() { + println(bytealg.PrimeRK) +} + +// Error: +// gno.land/p/demo/files/import12.gno:6:2: cannot import stdlib internal/ package outside of standard library diff --git a/gnovm/tests/files/import13.gno b/gnovm/tests/files/import13.gno new file mode 100644 index 00000000000..c0aa0e28a01 --- /dev/null +++ b/gnovm/tests/files/import13.gno @@ -0,0 +1,14 @@ +// PKGPATH: gno.land/p/demo + +package demo + +import ( + "github.com/gnolang/gno/_test/foo/internal/aa" +) + +func main() { + println(aa.A) +} + +// Error: +// gno.land/p/demo/files/import13.gno:6:2: internal/ packages can only be imported by packages rooted at the parent of "internal" diff --git a/gnovm/tests/files/import13b.gno b/gnovm/tests/files/import13b.gno new file mode 100644 index 00000000000..bedebb89b2f --- /dev/null +++ b/gnovm/tests/files/import13b.gno @@ -0,0 +1,14 @@ +// PKGPATH: gno.land/p/demo + +package demo + +import ( + "github.com/gnolang/gno/_test/foo/internal" +) + +func main() { + println(internal.A) +} + +// Error: +// gno.land/p/demo/files/import13b.gno:6:2: internal/ packages can only be imported by packages rooted at the parent of "internal" diff --git a/gnovm/tests/files/import14.gno b/gnovm/tests/files/import14.gno new file mode 100644 index 00000000000..07b57240bd1 --- /dev/null +++ b/gnovm/tests/files/import14.gno @@ -0,0 +1,17 @@ +// PKGPATH: github.com/gnolang/gno/_test/foo + +package foo + +import ( + "github.com/gnolang/gno/_test/foo/internal" + "github.com/gnolang/gno/_test/foo/internal/aa" +) + +func main() { + println(internal.A, internal.B) + println(aa.A) +} + +// Output: +// 123 456 +// 456 diff --git a/gnovm/tests/files/interface0.gno b/gnovm/tests/files/interface0.gno index 0054145561c..52e08e3d2d2 100644 --- a/gnovm/tests/files/interface0.gno +++ b/gnovm/tests/files/interface0.gno @@ -4,7 +4,7 @@ type sample struct { count int } -func run(inf interface{}, name string) { +func run(inf any, name string) { x := inf.(sample) println(x.count, name) } diff --git a/gnovm/tests/files/interface19.gno b/gnovm/tests/files/interface19.gno index a89d6dc7957..aebf76d68cf 100644 --- a/gnovm/tests/files/interface19.gno +++ b/gnovm/tests/files/interface19.gno @@ -2,7 +2,7 @@ package main import "fmt" -var I interface{} +var I any func main() { fmt.Printf("%T %v\n", I, I) diff --git a/gnovm/tests/files/interface20.gno b/gnovm/tests/files/interface20.gno index c3a9f396908..ce373aa8109 100644 --- a/gnovm/tests/files/interface20.gno +++ b/gnovm/tests/files/interface20.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - var a interface{} + var a any a = string("A") fmt.Println(a) } diff --git a/gnovm/tests/files/interface21.gno b/gnovm/tests/files/interface21.gno index 57f7a58ef74..ad8f473aa50 100644 --- a/gnovm/tests/files/interface21.gno +++ b/gnovm/tests/files/interface21.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - s := make([]interface{}, 1) + s := make([]any, 1) s[0] = 1 fmt.Println(s[0]) } diff --git a/gnovm/tests/files/interface22.gno b/gnovm/tests/files/interface22.gno index 7ba88efe4fc..5087a36ddd2 100644 --- a/gnovm/tests/files/interface22.gno +++ b/gnovm/tests/files/interface22.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - s := make([]interface{}, 0) + s := make([]any, 0) s = append(s, 1) fmt.Println(s[0]) } diff --git a/gnovm/tests/files/interface23.gno b/gnovm/tests/files/interface23.gno index fe02f20a2aa..9fa2dc8d04c 100644 --- a/gnovm/tests/files/interface23.gno +++ b/gnovm/tests/files/interface23.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - m := make(map[string]interface{}) + m := make(map[string]any) m["A"] = string("A") fmt.Println(m["A"]) } diff --git a/gnovm/tests/files/interface24.gno b/gnovm/tests/files/interface24.gno index 0ed7e12c3ab..de053dc5018 100644 --- a/gnovm/tests/files/interface24.gno +++ b/gnovm/tests/files/interface24.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - m := make(map[string]interface{}) + m := make(map[string]any) fmt.Println(m["B"]) } diff --git a/gnovm/tests/files/interface25.gno b/gnovm/tests/files/interface25.gno index cdc6e86e78c..76081e14541 100644 --- a/gnovm/tests/files/interface25.gno +++ b/gnovm/tests/files/interface25.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - m := make(map[string]interface{}) + m := make(map[string]any) m["A"] = 1 for _, v := range m { fmt.Println(v) diff --git a/gnovm/tests/files/interface26.gno b/gnovm/tests/files/interface26.gno index e1cec9dfae4..71c4ca2b089 100644 --- a/gnovm/tests/files/interface26.gno +++ b/gnovm/tests/files/interface26.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - s := make([]interface{}, 0) + s := make([]any, 0) s = append(s, 1) for _, v := range s { fmt.Println(v) diff --git a/gnovm/tests/files/interface29.gno b/gnovm/tests/files/interface29.gno index 8c64d49380d..cc3dda596ec 100644 --- a/gnovm/tests/files/interface29.gno +++ b/gnovm/tests/files/interface29.gno @@ -1,7 +1,7 @@ package main func main() { - var a interface{} + var a any println(a == nil) } diff --git a/gnovm/tests/files/interface30.gno b/gnovm/tests/files/interface30.gno index 7703581aca9..f982cb626af 100644 --- a/gnovm/tests/files/interface30.gno +++ b/gnovm/tests/files/interface30.gno @@ -1,7 +1,7 @@ package main func main() { - var a interface{} + var a any println(a != nil) } diff --git a/gnovm/tests/files/interface31.gno b/gnovm/tests/files/interface31.gno index a237133ee6c..dc79909a589 100644 --- a/gnovm/tests/files/interface31.gno +++ b/gnovm/tests/files/interface31.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - s := []interface{}{"test", 2} + s := []any{"test", 2} fmt.Println(s[0], s[1]) } diff --git a/gnovm/tests/files/interface32.gno b/gnovm/tests/files/interface32.gno index c270023f180..94bbca12081 100644 --- a/gnovm/tests/files/interface32.gno +++ b/gnovm/tests/files/interface32.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - s := [2]interface{}{"test", 2} + s := [2]any{"test", 2} fmt.Println(s[0], s[1]) } diff --git a/gnovm/tests/files/interface33.gno b/gnovm/tests/files/interface33.gno index 209d28430c8..5baf6cc210e 100644 --- a/gnovm/tests/files/interface33.gno +++ b/gnovm/tests/files/interface33.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - a := map[string]interface{}{"test": "test"} + a := map[string]any{"test": "test"} fmt.Println(a["test"]) } diff --git a/gnovm/tests/files/interface34.gno b/gnovm/tests/files/interface34.gno index bf727cbb84e..dc21d45e7fc 100644 --- a/gnovm/tests/files/interface34.gno +++ b/gnovm/tests/files/interface34.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - s := [2]interface{}{1: "test", 0: 2} + s := [2]any{1: "test", 0: 2} fmt.Println(s[0], s[1]) } diff --git a/gnovm/tests/files/interface35.gno b/gnovm/tests/files/interface35.gno index 303412d5bc9..50b1640ca6a 100644 --- a/gnovm/tests/files/interface35.gno +++ b/gnovm/tests/files/interface35.gno @@ -3,7 +3,7 @@ package main import "fmt" type T struct { - I interface{} + I any } func main() { diff --git a/gnovm/tests/files/interface39b.gno b/gnovm/tests/files/interface39b.gno index a87b9ff1d30..d782b7d1b1a 100644 --- a/gnovm/tests/files/interface39b.gno +++ b/gnovm/tests/files/interface39b.gno @@ -14,7 +14,7 @@ type Stringer interface { func main() { var f Stringer = &foo{bar: "bar"} - println(*((*foo)(f))) + println(*f.(*foo)) } // Output: diff --git a/gnovm/tests/files/interface42.gno b/gnovm/tests/files/interface42.gno index 1867baa7201..f8a6dce477d 100644 --- a/gnovm/tests/files/interface42.gno +++ b/gnovm/tests/files/interface42.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - v := interface{}(0) + v := any(0) fmt.Println(v) } diff --git a/gnovm/tests/files/interface43.gno b/gnovm/tests/files/interface43.gno index e7ee6438403..643d28487aa 100644 --- a/gnovm/tests/files/interface43.gno +++ b/gnovm/tests/files/interface43.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - v := interface{}(nil) + v := any(nil) fmt.Println(v) } diff --git a/gnovm/tests/files/interface44.gno b/gnovm/tests/files/interface44.gno index ba3fe46df50..735cd74cc6e 100644 --- a/gnovm/tests/files/interface44.gno +++ b/gnovm/tests/files/interface44.gno @@ -5,7 +5,7 @@ type S struct { } func main() { - var i interface{} = S{a: 1} + var i any = S{a: 1} s, ok := i.(S) if !ok { diff --git a/gnovm/tests/files/interface45.gno b/gnovm/tests/files/interface45.gno index 7611bf50994..3500075b7d2 100644 --- a/gnovm/tests/files/interface45.gno +++ b/gnovm/tests/files/interface45.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - var i interface{} = 1 + var i any = 1 var s struct{} s, _ = i.(struct{}) fmt.Println(s) diff --git a/gnovm/tests/files/interface47.gno b/gnovm/tests/files/interface47.gno new file mode 100644 index 00000000000..3cfb26f23bc --- /dev/null +++ b/gnovm/tests/files/interface47.gno @@ -0,0 +1,17 @@ +package main + +type Runner interface { + Run() +} + +type Swimmer interface { + Swim() +} + +func main() { + a := Runner(nil) + println(Swimmer(a)) +} + +// Error: +// main/files/interface47.gno:13:10: main.Runner does not implement main.Swimmer (missing method Swim) diff --git a/gnovm/tests/files/interface48.gno b/gnovm/tests/files/interface48.gno new file mode 100644 index 00000000000..62cc6619f64 --- /dev/null +++ b/gnovm/tests/files/interface48.gno @@ -0,0 +1,21 @@ +package main + +type Writer interface { + Write([]byte) (int, error) +} + +type Stringer interface { + String() string +} + +func main() { + var x interface { + Writer + Stringer + } + var w Writer = Writer(x) // explicit conversion + println(w) +} + +// Output: +// nil diff --git a/gnovm/tests/files/len0.gno b/gnovm/tests/files/len0.gno index 370964b832e..b5bbee62b14 100644 --- a/gnovm/tests/files/len0.gno +++ b/gnovm/tests/files/len0.gno @@ -1,6 +1,6 @@ package main -func f(a []int) interface{} { +func f(a []int) any { return len(a) } diff --git a/gnovm/tests/files/len8.gno b/gnovm/tests/files/len8.gno index 6ca5a6ae8fa..e48f276e394 100644 --- a/gnovm/tests/files/len8.gno +++ b/gnovm/tests/files/len8.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// unexpected type for len(): struct{A int;B int} +// unexpected type for len(): struct{A int; B int} diff --git a/gnovm/tests/files/len9.gno b/gnovm/tests/files/len9.gno new file mode 100644 index 00000000000..5dcc861e677 --- /dev/null +++ b/gnovm/tests/files/len9.gno @@ -0,0 +1,10 @@ +package main + +func main() { + var a map[string]string + + println(len(a)) +} + +// Output: +// 0 diff --git a/gnovm/tests/files/make0.gno b/gnovm/tests/files/make0.gno index d3484198c34..104c429db1a 100644 --- a/gnovm/tests/files/make0.gno +++ b/gnovm/tests/files/make0.gno @@ -1,6 +1,6 @@ package main -func f() interface{} { +func f() any { return make([]int, 2) } diff --git a/gnovm/tests/files/make1.gno b/gnovm/tests/files/make1.gno index 139c68f514a..c7fa68825ff 100644 --- a/gnovm/tests/files/make1.gno +++ b/gnovm/tests/files/make1.gno @@ -2,7 +2,7 @@ package main import "fmt" -func f() interface{} { +func f() any { return make(map[int]int) } diff --git a/gnovm/tests/files/map24.gno b/gnovm/tests/files/map24.gno index ea2f3905215..a2eaed88d1c 100644 --- a/gnovm/tests/files/map24.gno +++ b/gnovm/tests/files/map24.gno @@ -7,7 +7,7 @@ import ( func main() { jb := []byte(`{"property": "test"}`) - params := map[string]interface{}{"foo": 1} + params := map[string]any{"foo": 1} if err := json.Unmarshal(jb, ¶ms); err != nil { panic("marshal failed.") } diff --git a/gnovm/tests/files/map25.gno b/gnovm/tests/files/map25.gno index 50e23ff028a..ed6a9665ba5 100644 --- a/gnovm/tests/files/map25.gno +++ b/gnovm/tests/files/map25.gno @@ -7,7 +7,7 @@ import ( func main() { jb := []byte(`{"num": "2"}`) - params := map[string]interface{}{"foo": "1"} + params := map[string]any{"foo": "1"} if err := json.Unmarshal(jb, ¶ms); err != nil { panic(err) } diff --git a/gnovm/tests/files/map26.gno b/gnovm/tests/files/map26.gno index 8b9bcc6bc04..543117e556a 100644 --- a/gnovm/tests/files/map26.gno +++ b/gnovm/tests/files/map26.gno @@ -3,7 +3,7 @@ package main var m = map[string]int{"foo": 1, "bar": 2} func main() { - var a interface{} = m["foo"] + var a any = m["foo"] println(a.(int)) } diff --git a/gnovm/tests/files/map27.gno b/gnovm/tests/files/map27.gno index 578788d144e..5b5fccd5d06 100644 --- a/gnovm/tests/files/map27.gno +++ b/gnovm/tests/files/map27.gno @@ -4,7 +4,7 @@ import ( "fmt" ) -type fm map[string]interface{} +type fm map[string]any type foo struct{} @@ -13,7 +13,7 @@ func main() { a["foo"] = &foo{} fmt.Println(a["foo"]) - b := make(map[string]interface{}) + b := make(map[string]any) b["foo"] = &foo{} fmt.Println(b["foo"]) } diff --git a/gnovm/tests/files/map29.gno b/gnovm/tests/files/map29.gno index d33540715da..55372b86699 100644 --- a/gnovm/tests/files/map29.gno +++ b/gnovm/tests/files/map29.gno @@ -6,7 +6,7 @@ import ( ) type Item struct { - Object interface{} + Object any Expiry time.Duration } @@ -23,4 +23,4 @@ func main() { } // Output: -// {test 1000000000} +// {test 1s} diff --git a/gnovm/tests/files/method34.gno b/gnovm/tests/files/method34.gno index 314bf22b8ba..f10d73c0b6d 100644 --- a/gnovm/tests/files/method34.gno +++ b/gnovm/tests/files/method34.gno @@ -15,7 +15,7 @@ type Hi interface { func (r *Root) Hello() string { return "Hello " + r.Name } func main() { - var one interface{} = &One{Root{Name: "test2"}} + var one any = &One{Root{Name: "test2"}} println(one.(Hi).Hello()) } diff --git a/gnovm/tests/files/new2.gno b/gnovm/tests/files/new2.gno index 2f198ae00f4..2fa4f22e54d 100644 --- a/gnovm/tests/files/new2.gno +++ b/gnovm/tests/files/new2.gno @@ -1,6 +1,6 @@ package main -func f() interface{} { +func f() any { return new(int) } diff --git a/gnovm/tests/files/not2.gno b/gnovm/tests/files/not2.gno index b5405037d88..d831f2ab874 100644 --- a/gnovm/tests/files/not2.gno +++ b/gnovm/tests/files/not2.gno @@ -1,7 +1,7 @@ package main func main() { - var b interface{} = !(1 == 2) + var b any = !(1 == 2) println(b.(bool)) } diff --git a/gnovm/tests/files/op8.gno b/gnovm/tests/files/op8.gno index 1aa4e28b6e1..24a4455e81f 100644 --- a/gnovm/tests/files/op8.gno +++ b/gnovm/tests/files/op8.gno @@ -1,18 +1,18 @@ package main type I interface { - Get() interface{} + Get() any } type T struct{} -func (T) Get() interface{} { +func (T) Get() any { return nil } func main() { var i I = T{} - var ei interface{} + var ei any println(i != ei) } diff --git a/gnovm/tests/files/panic0a.gno b/gnovm/tests/files/panic0a.gno index 575bb7cce91..307a1ce9780 100644 --- a/gnovm/tests/files/panic0a.gno +++ b/gnovm/tests/files/panic0a.gno @@ -14,7 +14,7 @@ func main() { lit := []int{1} var ( pit *int = &vit - v interface{} + v any ) b := true v = 1 diff --git a/gnovm/tests/files/parse_err0.gno b/gnovm/tests/files/parse_err0.gno new file mode 100644 index 00000000000..71b310c6644 --- /dev/null +++ b/gnovm/tests/files/parse_err0.gno @@ -0,0 +1,10 @@ +// https://github.com/gnolang/gno/issues/3727 + +package main + +func () A() + +func main() {} + +// Error: +// files/parse_err0.gno:5:1: method has no receiver diff --git a/gnovm/tests/files/parse_err1.gno b/gnovm/tests/files/parse_err1.gno new file mode 100644 index 00000000000..efa21e883b0 --- /dev/null +++ b/gnovm/tests/files/parse_err1.gno @@ -0,0 +1,16 @@ +// https://github.com/gnolang/gno/issues/3731 + +package main + +type node struct { + r []int +} + +func (n *node) foo(targ, wndex int) { + _ = n.r[targ, wndex] +} + +func main() {} + +// Error: +// files/parse_err1.gno:10:6: invalid operation: more than one index diff --git a/gnovm/tests/files/parse_err2.gno b/gnovm/tests/files/parse_err2.gno new file mode 100644 index 00000000000..3e0f79d6935 --- /dev/null +++ b/gnovm/tests/files/parse_err2.gno @@ -0,0 +1,16 @@ +// https://github.com/gnolang/gno/issues/3751 + +package math + +import "testing" + +func Add(a, b int) int { + return a + b +} + +func TestAdd(t *testing.T) { + go Add(1, 1) +} + +// Error: +// files/parse_err2.gno:12:2: goroutines are not permitted diff --git a/gnovm/tests/files/pointer_eq.gno b/gnovm/tests/files/pointer_eq.gno index f144470e492..7e124dc16be 100644 --- a/gnovm/tests/files/pointer_eq.gno +++ b/gnovm/tests/files/pointer_eq.gno @@ -15,7 +15,7 @@ func main() { println(CheckNil()) } -func areEqual(v, w interface{}) bool { +func areEqual(v, w any) bool { return v == w } diff --git a/gnovm/tests/files/ptr11.gno b/gnovm/tests/files/ptr11.gno new file mode 100644 index 00000000000..98421562842 --- /dev/null +++ b/gnovm/tests/files/ptr11.gno @@ -0,0 +1,13 @@ +package main + +type S struct{} + +func (s S) String() string { return "hey" } + +func main() { + v := (*S)(nil) + v.String() +} + +// Error: +// nil pointer dereference diff --git a/gnovm/tests/files/ptr11a.gno b/gnovm/tests/files/ptr11a.gno new file mode 100644 index 00000000000..d027934474a --- /dev/null +++ b/gnovm/tests/files/ptr11a.gno @@ -0,0 +1,23 @@ +package main + +type S struct{} + +func (s S) String() string { return "hey" } + +type Stringer interface { + String() string +} + +func main() { + defer func() { + rec := recover() + if rec != nil { + println("recovered", rec) + } + }() + v := (*S)(nil) + println(Stringer(v).String()) +} + +// Output: +// recovered nil pointer dereference diff --git a/gnovm/tests/files/ptr11b.gno b/gnovm/tests/files/ptr11b.gno new file mode 100644 index 00000000000..e4d1a401325 --- /dev/null +++ b/gnovm/tests/files/ptr11b.gno @@ -0,0 +1,17 @@ +package main + +type S struct{} + +func (s S) String() string { return "hey" } + +type Stringer interface { + String() string +} + +func main() { + v := (*S)(nil) + println(Stringer(v).String()) +} + +// Error: +// nil pointer dereference diff --git a/gnovm/tests/files/ptrmap_1.gno b/gnovm/tests/files/ptrmap_1.gno new file mode 100644 index 00000000000..4e496a82f39 --- /dev/null +++ b/gnovm/tests/files/ptrmap_1.gno @@ -0,0 +1,27 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[int]int{} + i = 0 +) + +func AddToMap(value int) { + m[i] = value +} + +func GetFromMap() int { + return m[i] +} + +func init() { + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) +} + +// Output: +// true diff --git a/gnovm/tests/files/ptrmap_10.gno b/gnovm/tests/files/ptrmap_10.gno new file mode 100644 index 00000000000..92f61f557cb --- /dev/null +++ b/gnovm/tests/files/ptrmap_10.gno @@ -0,0 +1,21 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]string{} + a, b = 1, 2 + arr = [2]*int{&a, &b} +) + +func init() { + m[arr[0]] = "first key" +} + +func main() { + println(m[arr[0]]) // Output: first key + println(m[arr[1]] == "") +} + +// Output: +// first key +// true diff --git a/gnovm/tests/files/ptrmap_11.gno b/gnovm/tests/files/ptrmap_11.gno new file mode 100644 index 00000000000..6ffeed62f0b --- /dev/null +++ b/gnovm/tests/files/ptrmap_11.gno @@ -0,0 +1,22 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]string{} + a, b = 1, 2 + S = []*int{&a, &b} // slice + index = 0 +) + +func init() { + m[S[index]] = "first key" +} + +func main() { + println(m[S[index]]) // Output: first key + println(m[S[1]] == "") +} + +// Output: +// first key +// true diff --git a/gnovm/tests/files/ptrmap_12.gno b/gnovm/tests/files/ptrmap_12.gno new file mode 100644 index 00000000000..b0360fb5cef --- /dev/null +++ b/gnovm/tests/files/ptrmap_12.gno @@ -0,0 +1,21 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]string{} + a, b = 1, 2 + S = []*int{&a, &b} +) + +func init() { + m[S[0]] = "first key" +} + +func main() { + println(m[S[0]]) // Output: first key + println(m[S[1]] == "") +} + +// Output: +// first key +// true diff --git a/gnovm/tests/files/ptrmap_13.gno b/gnovm/tests/files/ptrmap_13.gno new file mode 100644 index 00000000000..0e0d24c6865 --- /dev/null +++ b/gnovm/tests/files/ptrmap_13.gno @@ -0,0 +1,22 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type MyStruct struct { + Index int +} + +var m = make(map[*int]string) +var a, b = 1, 2 +var s = []*int{&a, &b} +var myStruct = MyStruct{Index: 0} + +func init() { + m[s[myStruct.Index]] = "a" +} + +func main() { + println(m[s[myStruct.Index]]) +} + +// Output: +// a diff --git a/gnovm/tests/files/ptrmap_14.gno b/gnovm/tests/files/ptrmap_14.gno new file mode 100644 index 00000000000..5eb4c436def --- /dev/null +++ b/gnovm/tests/files/ptrmap_14.gno @@ -0,0 +1,20 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]string{} + a = 0 + ptr *int = &a // A pointer to an int + i1 **int = &ptr +) + +func init() { + m[*i1] = "first key" +} + +func main() { + println(m[*i1]) // Output: first key +} + +// Output: +// first key diff --git a/gnovm/tests/files/ptrmap_15.gno b/gnovm/tests/files/ptrmap_15.gno new file mode 100644 index 00000000000..b900fc4741f --- /dev/null +++ b/gnovm/tests/files/ptrmap_15.gno @@ -0,0 +1,23 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type MyStruct struct { + Key *int +} + +var ( + m = map[*int]string{} + i1 = MyStruct{Key: new(int)} +) + +func init() { + *i1.Key = 1 // Set the value of the pointer + m[i1.Key] = "first key" +} + +func main() { + println(m[i1.Key]) // Output: first key +} + +// Output: +// first key diff --git a/gnovm/tests/files/ptrmap_16.gno b/gnovm/tests/files/ptrmap_16.gno new file mode 100644 index 00000000000..40682a2c70e --- /dev/null +++ b/gnovm/tests/files/ptrmap_16.gno @@ -0,0 +1,24 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type Foo struct { + name string +} + +var ( + arr = [3]Foo{Foo{"a"}, Foo{"b"}, Foo{"c"}} + m = map[*Foo]string{} +) + +func init() { + m[&arr[0]] = "first key" +} + +func main() { + println(m[&arr[0]]) + println(m[&arr[1]] == "") +} + +// Output: +// first key +// true diff --git a/gnovm/tests/files/ptrmap_17.gno b/gnovm/tests/files/ptrmap_17.gno new file mode 100644 index 00000000000..4838ae988be --- /dev/null +++ b/gnovm/tests/files/ptrmap_17.gno @@ -0,0 +1,23 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type Foo struct { + name string +} + +var ( + arr = [3]Foo{Foo{"a"}, Foo{"b"}, Foo{"c"}} + m = map[*Foo]string{} + index = 0 +) + +func init() { + m[&arr[index]] = "first key" +} + +func main() { + println(m[&arr[index]]) +} + +// Output: +// first key diff --git a/gnovm/tests/files/ptrmap_18.gno b/gnovm/tests/files/ptrmap_18.gno new file mode 100644 index 00000000000..03c2d431d91 --- /dev/null +++ b/gnovm/tests/files/ptrmap_18.gno @@ -0,0 +1,33 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + i = 0 +) + +func AddToMap(value int) { + m[&i] = value +} + +func GetFromMap() int { + return m[&i] +} + +func init() { + i = 1 + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + i = 2 + r = GetFromMap() + println(r == 5) +} + +// Output: +// true +// true diff --git a/gnovm/tests/files/ptrmap_19.gno b/gnovm/tests/files/ptrmap_19.gno new file mode 100644 index 00000000000..2a3e6b5240f --- /dev/null +++ b/gnovm/tests/files/ptrmap_19.gno @@ -0,0 +1,37 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + i = 0 +) + +func AddToMap(value int) { + m[&i] = value +} + +func GetFromMap() int { + i := 0 + { + { + return m[&i] + } + } +} + +func init() { + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + i = 2 + r = GetFromMap() + println(r == 5) +} + +// Output: +// false +// false diff --git a/gnovm/tests/files/ptrmap_2.gno b/gnovm/tests/files/ptrmap_2.gno new file mode 100644 index 00000000000..006de1274b7 --- /dev/null +++ b/gnovm/tests/files/ptrmap_2.gno @@ -0,0 +1,35 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + i = new(int) +) + +func AddToMap(value int) { + m[i] = value +} + +func GetFromMap() int { + return m[i] +} + +func init() { + *i = 1 + AddToMap(5) +} + +// ----above is initialized and persisted before main is executed. + +func main() { + r := GetFromMap() + println(r == 5) + + *i = 2 // this changes TV, also Base of a pointer value + r = GetFromMap() + println(r == 5) +} + +// Output: +// true +// true diff --git a/gnovm/tests/files/ptrmap_20.gno b/gnovm/tests/files/ptrmap_20.gno new file mode 100644 index 00000000000..5efe9afaac0 --- /dev/null +++ b/gnovm/tests/files/ptrmap_20.gno @@ -0,0 +1,38 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[**int]int{} + i = new(int) +) + +func AddToMap(value int) { + m[&i] = value +} + +func GetFromMap() int { + return m[&i] +} + +func init() { + *i = 1 + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + var j = 0 + j1 := &j + println(m[&j1]) + + *i = 2 + r = GetFromMap() + println(r == 5) +} + +// Output: +// true +// 0 +// true \ No newline at end of file diff --git a/gnovm/tests/files/ptrmap_22.gno b/gnovm/tests/files/ptrmap_22.gno new file mode 100644 index 00000000000..653a6f2ffe8 --- /dev/null +++ b/gnovm/tests/files/ptrmap_22.gno @@ -0,0 +1,30 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type MyStruct struct { + Name string + Age int +} + +var ( + m = map[*MyStruct]string{} + i1 = &MyStruct{Name: "alice", Age: 2} +) + +func init() { + m[i1] = "first key" + println(m[i1]) +} + +func main() { + i2 := *i1 + println(m[&i2] == "") + + i1.Age = 3 + println(m[i1]) +} + +// Output: +// first key +// true +// first key diff --git a/gnovm/tests/files/ptrmap_23.gno b/gnovm/tests/files/ptrmap_23.gno new file mode 100644 index 00000000000..a587d924bc7 --- /dev/null +++ b/gnovm/tests/files/ptrmap_23.gno @@ -0,0 +1,17 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var arr = [2]int{1, 2} +var m = map[*[2]int]string{} + +func init() { + m[&arr] = "ok" +} + +func main() { + + println(m[&arr]) // Output: example +} + +// Output: +// ok diff --git a/gnovm/tests/files/ptrmap_24.gno b/gnovm/tests/files/ptrmap_24.gno new file mode 100644 index 00000000000..2c0890f47d0 --- /dev/null +++ b/gnovm/tests/files/ptrmap_24.gno @@ -0,0 +1,28 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type Foo struct { + name string +} + +type MyStruct struct { + Name string + Age int + key Foo +} + +var ( + m = map[*Foo]string{} + i1 = MyStruct{Name: "alice", Age: 2, key: Foo{name: "bob"}} +) + +func init() { + m[&i1.key] = "first key" +} + +func main() { + println(m[&i1.key]) +} + +// Output: +// first key diff --git a/gnovm/tests/files/ptrmap_25.gno b/gnovm/tests/files/ptrmap_25.gno new file mode 100644 index 00000000000..ebfa1940ce9 --- /dev/null +++ b/gnovm/tests/files/ptrmap_25.gno @@ -0,0 +1,34 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + a, b, c = 1, 2, 3 + s = []*int{&a, &b, &c} +) + +func AddToMap(value int) { + m[&*s[0]] = value +} + +func GetFromMap() int { + return m[&*s[0]] +} + +func init() { + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + a = 0 + + r = GetFromMap() + println(r == 5) +} + +// Output: +// true +// true diff --git a/gnovm/tests/files/ptrmap_26.gno b/gnovm/tests/files/ptrmap_26.gno new file mode 100644 index 00000000000..8b8faf97a2b --- /dev/null +++ b/gnovm/tests/files/ptrmap_26.gno @@ -0,0 +1,33 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + i = new(int) +) + +func AddToMap(value int) { + m[&*i] = value +} + +func GetFromMap() int { + return m[&*i] +} + +func init() { + *i = 1 + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + *i = 2 + r = GetFromMap() + println(r == 5) +} + +// Output: +// true +// true diff --git a/gnovm/tests/files/ptrmap_3.gno b/gnovm/tests/files/ptrmap_3.gno new file mode 100644 index 00000000000..d89c696e2f0 --- /dev/null +++ b/gnovm/tests/files/ptrmap_3.gno @@ -0,0 +1,34 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var ( + m = map[*int]int{} + i = new(int) +) + +func AddToMap(value int) { + m[i] = value +} + +func GetFromMap() int { + j := *i + return m[&j] +} + +func init() { + *i = 1 + AddToMap(5) +} + +func main() { + r := GetFromMap() + println(r == 5) + + *i = 2 + r = GetFromMap() + println(r == 5) +} + +// Output: +// false +// false diff --git a/gnovm/tests/files/ptrmap_4.gno b/gnovm/tests/files/ptrmap_4.gno new file mode 100644 index 00000000000..e8f6f2bfdb1 --- /dev/null +++ b/gnovm/tests/files/ptrmap_4.gno @@ -0,0 +1,30 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type S struct { + i int +} + +var m = make(map[*S]string) // Initialize the map +var sArr = make([]*S, 0, 4) // Use a slice of pointers + +func init() { + // Append pointers to the slice + sArr = append(sArr, &S{1}, &S{2}, &S{3}) + m[sArr[1]] = "a" +} + +func main() { + // Create a new slice without reallocating memory for existing elements + newArr := append(sArr[:1], sArr[2:]...) // same base array + + // Compare pointers directly + println(m[sArr[1]] == m[newArr[1]]) + println(m[sArr[1]] == "") + println(m[newArr[1]] == "") +} + +// Output: +// true +// true +// true diff --git a/gnovm/tests/files/ptrmap_5.gno b/gnovm/tests/files/ptrmap_5.gno new file mode 100644 index 00000000000..a38817867e5 --- /dev/null +++ b/gnovm/tests/files/ptrmap_5.gno @@ -0,0 +1,28 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type S struct { + i int +} + +var m = make(map[*S]string) // Initialize the map +var sArr = []*S{&S{1}, &S{2}, &S{3}} // Use a slice of pointers + +func init() { + m[sArr[1]] = "a" +} + +func main() { + // Create a new slice without reallocating memory for existing elements + newArr := append(sArr[:1], sArr[2:]...) // same base array + + // Compare pointers directly + println(sArr[1] == newArr[1]) + println(m[sArr[1]] == m[newArr[1]]) + println(m[newArr[1]] == "") +} + +// Output: +// true +// true +// true diff --git a/gnovm/tests/files/ptrmap_6.gno b/gnovm/tests/files/ptrmap_6.gno new file mode 100644 index 00000000000..1af7442e09f --- /dev/null +++ b/gnovm/tests/files/ptrmap_6.gno @@ -0,0 +1,37 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +type S struct { + i int +} + +var m = make(map[*S]string) // Initialize the map +var sArr = make([]*S, 0, 4) // Use a slice of pointers + +func init() { + a := S{1} + // Append pointers to the slice + sArr = append(sArr, &a, &S{2}, &S{3}) + println(&a == sArr[0]) + m[sArr[1]] = "a" +} + +func main() { + // Create a new slice without reallocating memory for existing elements + newArr := append(sArr[:1], sArr[2:]...) + + newArr = append(newArr, &S{4}) + newArr = append(newArr, &S{5}) + newArr = append(newArr, &S{6}) // reallocate array + + // Compare pointers directly + println(sArr[1] == newArr[1]) + println(m[sArr[1]] == m[newArr[1]]) + println(m[newArr[1]] == "") +} + +// Output: +// true +// true +// true +// true diff --git a/gnovm/tests/files/ptrmap_7.gno b/gnovm/tests/files/ptrmap_7.gno new file mode 100644 index 00000000000..443a112c6d8 --- /dev/null +++ b/gnovm/tests/files/ptrmap_7.gno @@ -0,0 +1,41 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +import "fmt" + +type S struct { + i int +} + +var m = make(map[*byte]string) // Initialize the map +var sArr = make([]*byte, 0, 4) // Use a slice of pointers +var a, b, c = byte('a'), byte('b'), byte('c') +var d, e, f = byte('d'), byte('e'), byte('f') + +func init() { + // Append pointers to the slice + sArr = append(sArr, &a, &b, &c) + m[sArr[1]] = "ok" + println(&b == sArr[1]) +} + +func main() { + // Create a new slice without reallocating memory for existing elements + newArr := append(sArr[:1], sArr[2:]...) + + newArr = append(newArr, &d) + newArr = append(newArr, &e) + // a, c, d, e, f + newArr = append(newArr, &f) // reallocation + + // Compare pointers directly + println(sArr[1] == newArr[1]) + println(m[sArr[1]] == m[newArr[1]]) + println(m[newArr[1]] == "") // underlying base array changed +} + +// Output: +// true +// true +// true +// true diff --git a/gnovm/tests/files/ptrmap_8.gno b/gnovm/tests/files/ptrmap_8.gno new file mode 100644 index 00000000000..0985644e9d9 --- /dev/null +++ b/gnovm/tests/files/ptrmap_8.gno @@ -0,0 +1,26 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var m = make(map[*int]string) +var m2 = make(map[int]int) + +var a, b, c = 1, 2, 3 +var s = []*int{&a, &b, &c} +var s2 []*int + +func init() { + m2[0] = 0 + m[s[m2[0]]] = "a" + s2 = append(s[:1], s[1:]...) +} + +func main() { + println(m[s[m2[0]]]) + println(s[m2[0]] == s2[m2[0]]) + println(m[s[m2[0]]] == m[s2[m2[0]]]) +} + +// Output: +// a +// true +// true diff --git a/gnovm/tests/files/ptrmap_9.gno b/gnovm/tests/files/ptrmap_9.gno new file mode 100644 index 00000000000..2756ad9acc7 --- /dev/null +++ b/gnovm/tests/files/ptrmap_9.gno @@ -0,0 +1,27 @@ +// PKGPATH: gno.land/r/ptr_map +package ptr_map + +var m = make(map[*int]string) +var m2 = make(map[int]int) + +var s []*int +var s2 []*int + +func init() { + s = append(s, new(int)) + m2[0] = 0 + // s[m2[0]] is pointer value, + m[s[m2[0]]] = "a" + s2 = append(s[:1], s[1:]...) +} + +func main() { + println(m[s[m2[0]]]) + println(s[m2[0]] == s2[m2[0]]) + println(m[s[m2[0]]] == m[s2[m2[0]]]) +} + +// Output: +// a +// true +// true diff --git a/gnovm/tests/files/recover15.gno b/gnovm/tests/files/recover15.gno index 74ba3ea66a2..01a78a55820 100644 --- a/gnovm/tests/files/recover15.gno +++ b/gnovm/tests/files/recover15.gno @@ -7,7 +7,7 @@ func main() { println("recover:", r) }() - var i interface{} = "hello" + var i any = "hello" _ = i.(int) // Panics because i holds a string, not an int } diff --git a/gnovm/tests/files/recover2.gno b/gnovm/tests/files/recover2.gno index 15ae6aeb8fd..41bb913d40b 100644 --- a/gnovm/tests/files/recover2.gno +++ b/gnovm/tests/files/recover2.gno @@ -3,7 +3,7 @@ package main func main() { println("hello") - var r interface{} = 1 + var r any = 1 r = recover() if r == nil { println("world") diff --git a/gnovm/tests/files/recover20.gno b/gnovm/tests/files/recover20.gno new file mode 100644 index 00000000000..58e5191d910 --- /dev/null +++ b/gnovm/tests/files/recover20.gno @@ -0,0 +1,16 @@ +package main + +func main() { + var p *int + if i := *p; i == 0 { + println("hey") + } +} + +// Error: +// nil pointer dereference + +// Stacktrace: +// panic: nil pointer dereference +// main() +// main/files/recover20.gno:5 diff --git a/gnovm/tests/files/recover3.gno b/gnovm/tests/files/recover3.gno index f10d4621098..4962249b6fa 100644 --- a/gnovm/tests/files/recover3.gno +++ b/gnovm/tests/files/recover3.gno @@ -5,7 +5,7 @@ import "fmt" func main() { println("hello") - var r interface{} = 1 + var r any = 1 r = recover() fmt.Printf("%v\n", r) if r == nil { diff --git a/gnovm/tests/files/recurse0.gno b/gnovm/tests/files/recurse0.gno index fe5a997b19f..a6786bbaaed 100644 --- a/gnovm/tests/files/recurse0.gno +++ b/gnovm/tests/files/recurse0.gno @@ -33,5 +33,5 @@ func main() { } // Output: -// (struct{(nil []main.T),(nil []*main.T),(nil map[string]main.T),(nil map[string]*main.T),(nil chan main.T),(nil chan *main.T),(nil *main.T),(nil func(.arg_0 main.T)( main.T)),(nil func(.arg_0 *main.T)( *main.T)),(struct{(nil []main.T),(nil []*main.T),(nil map[string]main.T),(nil map[string]*main.T),(nil chan main.T),(nil chan *main.T),(nil *main.T),(nil func(.arg_0 main.T)( main.T)),(nil func(.arg_0 *main.T)( *main.T))} main.U)} main.T) -// (struct{(nil []main.T),(nil []*main.T),(nil map[string]main.T),(nil map[string]*main.T),(nil chan main.T),(nil chan *main.T),(nil *main.T),(nil func(.arg_0 main.T)( main.T)),(nil func(.arg_0 *main.T)( *main.T))} main.U) +// (struct{(nil []main.T),(nil []*main.T),(nil map[string]main.T),(nil map[string]*main.T),(nil chan main.T),(nil chan *main.T),(nil *main.T),(nil func(main.T) main.T),(nil func(*main.T) *main.T),(struct{(nil []main.T),(nil []*main.T),(nil map[string]main.T),(nil map[string]*main.T),(nil chan main.T),(nil chan *main.T),(nil *main.T),(nil func(main.T) main.T),(nil func(*main.T) *main.T)} main.U)} main.T) +// (struct{(nil []main.T),(nil []*main.T),(nil map[string]main.T),(nil map[string]*main.T),(nil chan main.T),(nil chan *main.T),(nil *main.T),(nil func(main.T) main.T),(nil func(*main.T) *main.T)} main.U) diff --git a/gnovm/tests/files/recurse1.gno b/gnovm/tests/files/recurse1.gno new file mode 100644 index 00000000000..227576614b4 --- /dev/null +++ b/gnovm/tests/files/recurse1.gno @@ -0,0 +1,12 @@ +package main + +func main() { + var x any + for i := 0; i < 10000; i++ { + x = [1]any{x} + } + println(x) +} + +// Output: +// array[(array[(array[(array[(array[(array[(array[(array[(array[(array[(... [1]interface {})] [1]interface {})] [1]interface {})] [1]interface {})] [1]interface {})] [1]interface {})] [1]interface {})] [1]interface {})] [1]interface {})] [1]interface {})] diff --git a/gnovm/tests/files/std10.gno b/gnovm/tests/files/std10.gno index 7caf534c5ca..b549387feaa 100644 --- a/gnovm/tests/files/std10.gno +++ b/gnovm/tests/files/std10.gno @@ -7,8 +7,8 @@ func main() { // assert panic is recoverable println(recover()) }() - std.GetCallerAt(0) + std.CallerAt(0) } // Output: -// GetCallerAt requires positive arg +// CallerAt requires positive arg diff --git a/gnovm/tests/files/std11.gno b/gnovm/tests/files/std11.gno index ce02fa11ec3..70668fe443b 100644 --- a/gnovm/tests/files/std11.gno +++ b/gnovm/tests/files/std11.gno @@ -7,7 +7,7 @@ func main() { // assert panic is recoverable println(recover()) }() - std.GetCallerAt(42) + std.CallerAt(42) } // Output: diff --git a/gnovm/tests/files/std2.gno b/gnovm/tests/files/std2.gno index fe218f8b34a..71865fdf97d 100644 --- a/gnovm/tests/files/std2.gno +++ b/gnovm/tests/files/std2.gno @@ -3,7 +3,7 @@ package main import "std" func main() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() println(caller) } diff --git a/gnovm/tests/files/std3.gno b/gnovm/tests/files/std3.gno index e8d4bc31a12..a95c7395664 100644 --- a/gnovm/tests/files/std3.gno +++ b/gnovm/tests/files/std3.gno @@ -6,8 +6,8 @@ import ( ) func main() { - caller := std.GetOrigCaller() - caller2 := std.GetOrigCaller() + caller := std.OriginCaller() + caller2 := std.OriginCaller() cmp := bytes.Compare([]byte(caller), []byte(caller2)) println(cmp) } diff --git a/gnovm/tests/files/std4.gno b/gnovm/tests/files/std4.gno index d09bc6251c0..aef32507b08 100644 --- a/gnovm/tests/files/std4.gno +++ b/gnovm/tests/files/std4.gno @@ -5,7 +5,7 @@ import ( ) func main() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) } diff --git a/gnovm/tests/files/std5.gno b/gnovm/tests/files/std5.gno index 2f9e98bb4ec..81e066c3762 100644 --- a/gnovm/tests/files/std5.gno +++ b/gnovm/tests/files/std5.gno @@ -5,18 +5,18 @@ import ( ) func main() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) } // Stacktrace: // panic: frame not found -// callerAt(n) +// callerAt(n) // gonative:std.callerAt -// std.GetCallerAt(2) -// std/native.gno:45 +// std.CallerAt(2) +// std/native.gno:41 // main() // main/files/std5.gno:10 diff --git a/gnovm/tests/files/std6.gno b/gnovm/tests/files/std6.gno index 20943f47d28..27c64503b13 100644 --- a/gnovm/tests/files/std6.gno +++ b/gnovm/tests/files/std6.gno @@ -3,9 +3,9 @@ package main import "std" func inner() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) } diff --git a/gnovm/tests/files/std7.gno b/gnovm/tests/files/std7.gno index 9d602cc2039..ce767fe59e9 100644 --- a/gnovm/tests/files/std7.gno +++ b/gnovm/tests/files/std7.gno @@ -7,11 +7,11 @@ import ( ) func inner() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) - caller3 := std.GetCallerAt(3) + caller3 := std.CallerAt(3) println(caller3) } diff --git a/gnovm/tests/files/std8.gno b/gnovm/tests/files/std8.gno index dfc2b8ca5fd..509da757e87 100644 --- a/gnovm/tests/files/std8.gno +++ b/gnovm/tests/files/std8.gno @@ -7,13 +7,13 @@ import ( ) func inner() { - caller1 := std.GetCallerAt(1) + caller1 := std.CallerAt(1) println(caller1) - caller2 := std.GetCallerAt(2) + caller2 := std.CallerAt(2) println(caller2) - caller3 := std.GetCallerAt(3) + caller3 := std.CallerAt(3) println(caller3) - caller4 := std.GetCallerAt(4) + caller4 := std.CallerAt(4) println(caller4) } @@ -23,10 +23,10 @@ func main() { // Stacktrace: // panic: frame not found -// callerAt(n) +// callerAt(n) // gonative:std.callerAt -// std.GetCallerAt(4) -// std/native.gno:45 +// std.CallerAt(4) +// std/native.gno:41 // fn() // main/files/std8.gno:16 // testutils.WrapCall(inner) diff --git a/gnovm/tests/files/struct13.gno b/gnovm/tests/files/struct13.gno index 81092ba8406..85515555f50 100644 --- a/gnovm/tests/files/struct13.gno +++ b/gnovm/tests/files/struct13.gno @@ -16,4 +16,4 @@ func main() { } // Output: -// 0 +// 0s diff --git a/gnovm/tests/files/struct59.gno b/gnovm/tests/files/struct59.gno new file mode 100644 index 00000000000..7c48b0d6ef2 --- /dev/null +++ b/gnovm/tests/files/struct59.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +type X struct { + v string +} + +func main() { + x := X{v: "a"} + fmt.Printf("test %#v\n", x) +} + +// Output: +// test main.X{v:"a"} diff --git a/gnovm/tests/files/struct60.gno b/gnovm/tests/files/struct60.gno new file mode 100644 index 00000000000..351120ddf77 --- /dev/null +++ b/gnovm/tests/files/struct60.gno @@ -0,0 +1,28 @@ +package main + +type Outfit struct { + Scarf string + Shirt string + Belts string + Strap string + Pants string + Socks string + Shoes string +} + +func main() { + s := Outfit{ + // some fields are out of order. + // some fields are left unset. + Scarf: "scarf", + Shirt: "shirt", + Shoes: "shoes", + Socks: "socks", + } + // some fields out of order are used. + // some fields left unset are used. + print(s.Shoes + "," + s.Shirt + "," + s.Pants + "," + s.Scarf) +} + +// Output: +// shoes,shirt,,scarf diff --git a/gnovm/tests/files/struct61.gno b/gnovm/tests/files/struct61.gno new file mode 100644 index 00000000000..234f0d181d5 --- /dev/null +++ b/gnovm/tests/files/struct61.gno @@ -0,0 +1,21 @@ +package main + +type MyStruct struct { + FieldA string + FieldB string +} + +func myStruct(a, b string) MyStruct { + return MyStruct{ + FieldA: a, + FieldB: b, + } +} + +func main() { + s := myStruct("aaa", "bbb") + print(s.FieldA + "," + s.FieldB) +} + +// Output: +// aaa,bbb diff --git a/gnovm/tests/files/switch10.gno b/gnovm/tests/files/switch10.gno index e10b8c48c61..ec3125cfe57 100644 --- a/gnovm/tests/files/switch10.gno +++ b/gnovm/tests/files/switch10.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} = "truc" + var i any = "truc" switch a := i.(type) { case string: diff --git a/gnovm/tests/files/switch11.gno b/gnovm/tests/files/switch11.gno index b61e02e9d7b..2dac28aea08 100644 --- a/gnovm/tests/files/switch11.gno +++ b/gnovm/tests/files/switch11.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} = "truc" + var i any = "truc" switch b := 2; a := i.(type) { case string: diff --git a/gnovm/tests/files/switch12.gno b/gnovm/tests/files/switch12.gno index 51efbc7a10d..b531402aa77 100644 --- a/gnovm/tests/files/switch12.gno +++ b/gnovm/tests/files/switch12.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} + var i any switch a := i.(type) { case string: diff --git a/gnovm/tests/files/switch13.gno b/gnovm/tests/files/switch13.gno index f69f09d1037..2204614867f 100644 --- a/gnovm/tests/files/switch13.gno +++ b/gnovm/tests/files/switch13.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} + var i any switch a := i.(type) { case string: diff --git a/gnovm/tests/files/switch22.gno b/gnovm/tests/files/switch22.gno index fe253f98734..a48ee76a42d 100644 --- a/gnovm/tests/files/switch22.gno +++ b/gnovm/tests/files/switch22.gno @@ -4,7 +4,7 @@ type T struct { Name string } -func f(t interface{}) { +func f(t any) { switch ext := t.(type) { case *T: println("*T", ext.Name) diff --git a/gnovm/tests/files/switch33.gno b/gnovm/tests/files/switch33.gno index 2c95808aaf3..1fe3691f75b 100644 --- a/gnovm/tests/files/switch33.gno +++ b/gnovm/tests/files/switch33.gno @@ -1,7 +1,7 @@ package main func main() { - var a interface{} + var a any switch a.(type) { } println("bye") diff --git a/gnovm/tests/files/switch34.gno b/gnovm/tests/files/switch34.gno index 5807b383880..f5adeeb569c 100644 --- a/gnovm/tests/files/switch34.gno +++ b/gnovm/tests/files/switch34.gno @@ -1,7 +1,7 @@ package main func main() { - var a interface{} + var a any switch a.(type) { case []int: case []string: diff --git a/gnovm/tests/files/switch41.gno b/gnovm/tests/files/switch41.gno index f5d9d885de5..3c789086679 100644 --- a/gnovm/tests/files/switch41.gno +++ b/gnovm/tests/files/switch41.gno @@ -7,7 +7,7 @@ func (s) hello() string { } func main() { - var v interface{} + var v any v = s{} switch v := v.(type) { case interface{ hello() string }: diff --git a/gnovm/tests/files/switch7.gno b/gnovm/tests/files/switch7.gno index 73f46441554..05a55b86a56 100644 --- a/gnovm/tests/files/switch7.gno +++ b/gnovm/tests/files/switch7.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} = "truc" + var i any = "truc" switch i.(type) { case string: diff --git a/gnovm/tests/files/switch9.gno b/gnovm/tests/files/switch9.gno index 5b05316b0b9..7be1f8a3106 100644 --- a/gnovm/tests/files/switch9.gno +++ b/gnovm/tests/files/switch9.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} = "truc" + var i any = "truc" switch i.(type) { case string: diff --git a/gnovm/tests/files/time11.gno b/gnovm/tests/files/time11.gno index 9f6126fe87b..7dcd5b66259 100644 --- a/gnovm/tests/files/time11.gno +++ b/gnovm/tests/files/time11.gno @@ -14,4 +14,4 @@ func main() { // Output: // 30m0s -// df: 1800000000000 int64 +// df: 30m0s time.Duration diff --git a/gnovm/tests/files/type1.gno b/gnovm/tests/files/type1.gno index 3116d632aa6..ad2305a711d 100644 --- a/gnovm/tests/files/type1.gno +++ b/gnovm/tests/files/type1.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - var i interface{} = "hello" + var i any = "hello" s := i.(string) fmt.Println(s) } diff --git a/gnovm/tests/files/type11.gno b/gnovm/tests/files/type11.gno index 28aaee838dc..3ace62fc388 100644 --- a/gnovm/tests/files/type11.gno +++ b/gnovm/tests/files/type11.gno @@ -14,4 +14,4 @@ func main() { } // Output: -// [10]int64 +// [10]time.Duration diff --git a/gnovm/tests/files/type23b.gno b/gnovm/tests/files/type23b.gno index 60301ef1d39..259dc8cd3fe 100644 --- a/gnovm/tests/files/type23b.gno +++ b/gnovm/tests/files/type23b.gno @@ -6,8 +6,8 @@ import ( ) func main() { - var v1 interface{} = 1 - var v2 interface{} + var v1 any = 1 + var v2 any var v3 http.ResponseWriter = httptest.NewRecorder() if r1, ok := v1.(string); ok { diff --git a/gnovm/tests/files/type24b.gno b/gnovm/tests/files/type24b.gno index fa31104e4ff..648acbca28f 100644 --- a/gnovm/tests/files/type24b.gno +++ b/gnovm/tests/files/type24b.gno @@ -19,7 +19,7 @@ func assertInt() { fmt.Println(r) }() - var v interface{} = 1 + var v any = 1 println(v.(string)) } @@ -29,7 +29,7 @@ func assertNil() { fmt.Println(r) }() - var v interface{} + var v any println(v.(string)) } @@ -45,5 +45,5 @@ func assertValue() { // Output: // int is not of type string -// interface{} is not of type string -// *github.com/gnolang/gno/_test/net/http/httptest.ResponseRecorder doesn't implement interface{Push func(string;*github.com/gnolang/gno/_test/net/http.PushOptions)(.uverse.error)} (missing method Push) +// interface {} is not of type string +// *github.com/gnolang/gno/_test/net/http/httptest.ResponseRecorder doesn't implement interface {Push func(string, *github.com/gnolang/gno/_test/net/http.PushOptions) .uverse.error} (missing method Push) diff --git a/gnovm/tests/files/type40.gno b/gnovm/tests/files/type40.gno index fe312e220e0..8ad8e758d6e 100644 --- a/gnovm/tests/files/type40.gno +++ b/gnovm/tests/files/type40.gno @@ -10,7 +10,7 @@ type ( Pointer2 = Pointer // Interface - Interface = interface{} + Interface = any Interface2 = Interface // S diff --git a/gnovm/tests/files/type7.gno b/gnovm/tests/files/type7.gno index ec0a41b7b93..bf1060ff759 100644 --- a/gnovm/tests/files/type7.gno +++ b/gnovm/tests/files/type7.gno @@ -3,7 +3,7 @@ package main import "fmt" func main() { - var i interface{} = "hello" + var i any = "hello" if s, ok := i.(string); ok { fmt.Println(s, ok) } diff --git a/gnovm/tests/files/type_alias.gno b/gnovm/tests/files/type_alias.gno index e95c54126ec..09918f6d591 100644 --- a/gnovm/tests/files/type_alias.gno +++ b/gnovm/tests/files/type_alias.gno @@ -6,7 +6,8 @@ import "gno.land/p/demo/uassert" type TestingT = uassert.TestingT func main() { - println(TestingT) + println("ok") } -// No need for output; not panicking is passing. +// Output: +// ok diff --git a/gnovm/tests/files/typeassert4.gno b/gnovm/tests/files/typeassert4.gno index a137d3833ee..90d2165da5b 100644 --- a/gnovm/tests/files/typeassert4.gno +++ b/gnovm/tests/files/typeassert4.gno @@ -16,7 +16,7 @@ func (s *ValueSetter) Set(value string) { s.value = value } -func cmpSetter(i interface{}) { +func cmpSetter(i any) { if _, ok := i.(Setter); ok { println("ok") } else { @@ -26,7 +26,7 @@ func cmpSetter(i interface{}) { func main() { var ( - i interface{} + i any setter Setter setterClone SetterClone valueSetter ValueSetter diff --git a/gnovm/tests/files/typeassert4a.gno b/gnovm/tests/files/typeassert4a.gno index 09e9ae06aa1..25adab25132 100644 --- a/gnovm/tests/files/typeassert4a.gno +++ b/gnovm/tests/files/typeassert4a.gno @@ -16,7 +16,7 @@ func (s *ValueSetter) Set(value string) { s.value = value } -func cmpSetter(i interface{}) { +func cmpSetter(i any) { defer func() { if r := recover(); r != nil { println(r) @@ -30,7 +30,7 @@ func cmpSetter(i interface{}) { func main() { var ( - i interface{} + i any setter Setter setterClone SetterClone valueSetter ValueSetter @@ -58,5 +58,5 @@ func main() { // interface conversion: interface is nil, not main.Setter // interface conversion: interface is nil, not main.Setter // ok -// main.ValueSetter doesn't implement interface{Set func(string)()} (method Set has pointer receiver) +// main.ValueSetter doesn't implement interface {Set func(string)} (method Set has pointer receiver) // ok diff --git a/gnovm/tests/files/typeassert6.gno b/gnovm/tests/files/typeassert6.gno index 5faa392a703..e80bfffdaca 100644 --- a/gnovm/tests/files/typeassert6.gno +++ b/gnovm/tests/files/typeassert6.gno @@ -5,7 +5,7 @@ type A interface { } func main() { - var v interface{} + var v any v = 9 if _, ok := v.(A); !ok { diff --git a/gnovm/tests/files/typeassert6a.gno b/gnovm/tests/files/typeassert6a.gno index e55f077c334..47fc73026e5 100644 --- a/gnovm/tests/files/typeassert6a.gno +++ b/gnovm/tests/files/typeassert6a.gno @@ -11,7 +11,7 @@ func test1() { } }() - var v interface{} + var v any v = 9 _ = v.(A) } @@ -23,7 +23,7 @@ func test2() { } }() - var v interface{} + var v any vp := new(int) *vp = 99 v = vp @@ -36,5 +36,5 @@ func main() { } // Output: -// int doesn't implement interface{Do func(string)()} (missing method Do) -// *int doesn't implement interface{Do func(string)()} (missing method Do) +// int doesn't implement interface {Do func(string)} (missing method Do) +// *int doesn't implement interface {Do func(string)} (missing method Do) diff --git a/gnovm/tests/files/typeassert7.gno b/gnovm/tests/files/typeassert7.gno index ed79ecf2e2e..ea9e2e2b3c6 100644 --- a/gnovm/tests/files/typeassert7.gno +++ b/gnovm/tests/files/typeassert7.gno @@ -7,7 +7,7 @@ import ( func main() { { - var v interface{} + var v any var r io.Reader r = bytes.NewBuffer([]byte("hello")) v = r @@ -18,7 +18,7 @@ func main() { } } { - var v interface{} + var v any var r io.Reader v = r if _, ok := v.(io.Reader); ok { @@ -28,7 +28,7 @@ func main() { } } { - var v interface{} + var v any v = bytes.NewBuffer([]byte("hello")) if _, ok := v.(io.Reader); ok { println("ok") diff --git a/gnovm/tests/files/typeassert7a.gno b/gnovm/tests/files/typeassert7a.gno index 6e0aa0e8dca..f416ec4d841 100644 --- a/gnovm/tests/files/typeassert7a.gno +++ b/gnovm/tests/files/typeassert7a.gno @@ -5,7 +5,7 @@ import ( "io" ) -func testImpl(v interface{}) { +func testImpl(v any) { defer func() { if r := recover(); r != nil { println(r) @@ -18,20 +18,20 @@ func testImpl(v interface{}) { func main() { { - var v interface{} + var v any var r io.Reader r = bytes.NewBuffer([]byte("hello")) v = r testImpl(v) } { - var v interface{} + var v any var r io.Reader v = r testImpl(v) } { - var v interface{} + var v any v = bytes.NewBuffer([]byte("hello")) testImpl(v) } diff --git a/gnovm/tests/files/types/add_d4.gno b/gnovm/tests/files/types/add_d4.gno index a1ff8e09ae7..fde1e70229b 100644 --- a/gnovm/tests/files/types/add_d4.gno +++ b/gnovm/tests/files/types/add_d4.gno @@ -1,7 +1,7 @@ package main var a int -var b interface{} +var b any func main() { println(b + a) diff --git a/gnovm/tests/files/types/assign_nil.gno b/gnovm/tests/files/types/assign_nil.gno index ecbca26dad0..5d742b6f813 100644 --- a/gnovm/tests/files/types/assign_nil.gno +++ b/gnovm/tests/files/types/assign_nil.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} + var i any i = 4 var j int j, nil = i.(int) diff --git a/gnovm/tests/files/types/assign_nil2.gno b/gnovm/tests/files/types/assign_nil2.gno index a1559d9de1f..9b8cc1cc196 100644 --- a/gnovm/tests/files/types/assign_nil2.gno +++ b/gnovm/tests/files/types/assign_nil2.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} + var i any i = 4 var ok bool nil, nil = i.(int) diff --git a/gnovm/tests/files/types/assign_type_assertion.gno b/gnovm/tests/files/types/assign_type_assertion.gno index de989551235..3b60614a166 100644 --- a/gnovm/tests/files/types/assign_type_assertion.gno +++ b/gnovm/tests/files/types/assign_type_assertion.gno @@ -5,7 +5,7 @@ import ( ) func main() { - var i interface{} = "Hello, world!" + var i any = "Hello, world!" // Attempt to assert the type of i to string var n int diff --git a/gnovm/tests/files/types/assign_type_assertion_b.gno b/gnovm/tests/files/types/assign_type_assertion_b.gno index 1cd70bde4ec..959e4f4c722 100644 --- a/gnovm/tests/files/types/assign_type_assertion_b.gno +++ b/gnovm/tests/files/types/assign_type_assertion_b.gno @@ -29,4 +29,4 @@ func main() { } // Error: -// main/files/types/assign_type_assertion_b.gno:22:2: cannot use interface{IsSet func()(bool)} as int +// main/files/types/assign_type_assertion_b.gno:22:2: cannot use interface {IsSet func() bool} as int diff --git a/gnovm/tests/files/types/assign_type_assertion_c.gno b/gnovm/tests/files/types/assign_type_assertion_c.gno index d1c1cadcb91..83967073603 100644 --- a/gnovm/tests/files/types/assign_type_assertion_c.gno +++ b/gnovm/tests/files/types/assign_type_assertion_c.gno @@ -30,4 +30,4 @@ func main() { } // Error: -// main/files/types/assign_type_assertion_c.gno:23:2: interface{IsSet func()(bool)} does not implement interface{IsNotSet func()(bool)} (missing method IsNotSet) +// main/files/types/assign_type_assertion_c.gno:23:2: interface {IsSet func() bool} does not implement interface {IsNotSet func() bool} (missing method IsNotSet) diff --git a/gnovm/tests/files/types/cmp_iface_0_stdlibs.gno b/gnovm/tests/files/types/cmp_iface_0_stdlibs.gno new file mode 100644 index 00000000000..fb4ac682243 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_0_stdlibs.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if Error(0) == errCmp { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/cmp_iface_2.gno b/gnovm/tests/files/types/cmp_iface_2.gno index f3bf14b2b7b..cb3371a9796 100644 --- a/gnovm/tests/files/types/cmp_iface_2.gno +++ b/gnovm/tests/files/types/cmp_iface_2.gno @@ -28,5 +28,5 @@ func main() { } // Output: -// int64 +// main.Error // what the firetruck? diff --git a/gnovm/tests/files/types/cmp_iface_3_stdlibs.gno b/gnovm/tests/files/types/cmp_iface_3_stdlibs.gno new file mode 100644 index 00000000000..9c4cb0e5ea0 --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_3_stdlibs.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if Error(1) == errCmp { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Output: +// something else diff --git a/gnovm/tests/files/types/cmp_iface_4.gno b/gnovm/tests/files/types/cmp_iface_4.gno index a4ae0463291..9b22269fd15 100644 --- a/gnovm/tests/files/types/cmp_iface_4.gno +++ b/gnovm/tests/files/types/cmp_iface_4.gno @@ -12,7 +12,7 @@ func (e Error) Error() string { // both not const, and both interface func main() { - var l interface{} + var l any if l == Error(0) { println("what the firetruck?") } else { diff --git a/gnovm/tests/files/types/cmp_iface_5_stdlibs.gno b/gnovm/tests/files/types/cmp_iface_5_stdlibs.gno new file mode 100644 index 00000000000..e706c74808e --- /dev/null +++ b/gnovm/tests/files/types/cmp_iface_5_stdlibs.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" + "strconv" +) + +type Error int64 + +func (e Error) Error() string { + return "error: " + strconv.Itoa(int(e)) +} + +var errCmp = errors.New("XXXX") + +// special case: +// one is interface +func main() { + if errCmp == int64(1) { + println("what the firetruck?") + } else { + println("something else") + } +} + +// Error: +// main/files/types/cmp_iface_5_stdlibs.gno:19:5: int64 does not implement .uverse.error (missing method Error) diff --git a/gnovm/tests/files/types/cmp_iface_7.gno b/gnovm/tests/files/types/cmp_iface_7.gno index a0ba3e8a0d3..680c629f1fa 100644 --- a/gnovm/tests/files/types/cmp_iface_7.gno +++ b/gnovm/tests/files/types/cmp_iface_7.gno @@ -2,7 +2,7 @@ package main import "fmt" -func check(v1, v2 interface{}) bool { +func check(v1, v2 any) bool { return v1 == v2 } diff --git a/gnovm/tests/files/types/cmp_typeswitch.gno b/gnovm/tests/files/types/cmp_typeswitch.gno index 721dfc9579a..549c7c44341 100644 --- a/gnovm/tests/files/types/cmp_typeswitch.gno +++ b/gnovm/tests/files/types/cmp_typeswitch.gno @@ -1,7 +1,7 @@ package main func main() { - var l interface{} + var l any l = int64(0) switch val := l.(type) { diff --git a/gnovm/tests/files/types/cmp_typeswitch_a.gno b/gnovm/tests/files/types/cmp_typeswitch_a.gno index f70fcb3d3d6..1934f16abf2 100644 --- a/gnovm/tests/files/types/cmp_typeswitch_a.gno +++ b/gnovm/tests/files/types/cmp_typeswitch_a.gno @@ -1,7 +1,7 @@ package main func main() { - var l interface{} + var l any l = int(0) switch val := l.(type) { diff --git a/gnovm/tests/files/types/eql_0f15.gno b/gnovm/tests/files/types/eql_0f15.gno index 36077127332..5172a9dd731 100644 --- a/gnovm/tests/files/types/eql_0f15.gno +++ b/gnovm/tests/files/types/eql_0f15.gno @@ -1,15 +1,15 @@ package main -var a [2]interface{} -var c [2]interface{} +var a [2]any +var c [2]any -func gen() interface{} { +func gen() any { return 1 } func main() { - a = [2]interface{}{gen(), gen()} - c = [2]interface{}{gen(), gen()} + a = [2]any{gen(), gen()} + c = [2]any{gen(), gen()} println(a == c) } diff --git a/gnovm/tests/files/types/eql_0f29.gno b/gnovm/tests/files/types/eql_0f29.gno index e754c026c81..0bf4af6fd24 100644 --- a/gnovm/tests/files/types/eql_0f29.gno +++ b/gnovm/tests/files/types/eql_0f29.gno @@ -12,7 +12,7 @@ func (e Error) Error() string { // both not const, and both interface func main() { - var l interface{} + var l any if l > Error(0) { println("what the firetruck?") } else { diff --git a/gnovm/tests/files/types/eql_0f2d.gno b/gnovm/tests/files/types/eql_0f2d.gno index f3bf14b2b7b..cb3371a9796 100644 --- a/gnovm/tests/files/types/eql_0f2d.gno +++ b/gnovm/tests/files/types/eql_0f2d.gno @@ -28,5 +28,5 @@ func main() { } // Output: -// int64 +// main.Error // what the firetruck? diff --git a/gnovm/tests/files/types/eql_0f2e.gno b/gnovm/tests/files/types/eql_0f2e.gno index c9c24dcd4eb..d8e0223e5d2 100644 --- a/gnovm/tests/files/types/eql_0f2e.gno +++ b/gnovm/tests/files/types/eql_0f2e.gno @@ -28,5 +28,5 @@ func main() { } // Output: -// int64 +// main.Error // what the firetruck? diff --git a/gnovm/tests/files/types/eql_0f33.gno b/gnovm/tests/files/types/eql_0f33.gno index e7aac0cf069..00b9187344e 100644 --- a/gnovm/tests/files/types/eql_0f33.gno +++ b/gnovm/tests/files/types/eql_0f33.gno @@ -4,7 +4,7 @@ import "fmt" // var f func() var a *struct{} -var b interface{} +var b any var c map[string]int var s []int diff --git a/gnovm/tests/files/types/eql_0f34.gno b/gnovm/tests/files/types/eql_0f34.gno index 8b5d3830091..0d6310b221b 100644 --- a/gnovm/tests/files/types/eql_0f34.gno +++ b/gnovm/tests/files/types/eql_0f34.gno @@ -4,7 +4,7 @@ import "fmt" // var f func() var a *struct{} -var b interface{} +var b any var c map[string]int var s []int diff --git a/gnovm/tests/files/types/eql_0f35.gno b/gnovm/tests/files/types/eql_0f35.gno index c57d3e9d375..d15524d4f90 100644 --- a/gnovm/tests/files/types/eql_0f35.gno +++ b/gnovm/tests/files/types/eql_0f35.gno @@ -31,4 +31,4 @@ func main() { // Output: // Recovered. Error: -// 0 +// error: 0 diff --git a/gnovm/tests/files/types/eql_0f37.gno b/gnovm/tests/files/types/eql_0f37.gno index 7fb051961ee..40e14c40444 100644 --- a/gnovm/tests/files/types/eql_0f37.gno +++ b/gnovm/tests/files/types/eql_0f37.gno @@ -17,7 +17,7 @@ func (e Error1) Error() string { return "error: " + strconv.Itoa(int(e)) } -func get() interface{} { +func get() any { return Error1(0) } diff --git a/gnovm/tests/files/types/eql_0f48.gno b/gnovm/tests/files/types/eql_0f48.gno index fbf4f164c9d..7c051e7226e 100644 --- a/gnovm/tests/files/types/eql_0f48.gno +++ b/gnovm/tests/files/types/eql_0f48.gno @@ -1,6 +1,6 @@ package main -func testEql(want, got interface{}) { +func testEql(want, got any) { if want != got { println(false) } else { @@ -13,7 +13,7 @@ func gen() error { return nil } -func main() { // about untyped nil to (interface{})typed-nil, no support for native for now. +func main() { // about untyped nil to (any)typed-nil, no support for native for now. r := gen() testEql(r, error(nil)) } diff --git a/gnovm/tests/files/types/eql_0f49.gno b/gnovm/tests/files/types/eql_0f49.gno index b5a4bf4ed05..b4d6f7e3972 100644 --- a/gnovm/tests/files/types/eql_0f49.gno +++ b/gnovm/tests/files/types/eql_0f49.gno @@ -14,6 +14,8 @@ func main() { } +// Output: +// true // true // true // true diff --git a/gnovm/tests/files/types/eql_iface.gno b/gnovm/tests/files/types/eql_iface.gno index d3d41348a62..64bd3aa9437 100644 --- a/gnovm/tests/files/types/eql_iface.gno +++ b/gnovm/tests/files/types/eql_iface.gno @@ -5,7 +5,7 @@ type Foo struct { } func main() { - var l interface{} = 1 + var l any = 1 println(1 == l) } diff --git a/gnovm/tests/files/types/iface_eql.gno b/gnovm/tests/files/types/iface_eql.gno index 97daa27c184..edfd9636949 100644 --- a/gnovm/tests/files/types/iface_eql.gno +++ b/gnovm/tests/files/types/iface_eql.gno @@ -1,7 +1,7 @@ package main func main() { - var l interface{} = 1 + var l any = 1 println(int8(1) == l) } diff --git a/gnovm/tests/files/types/runtime_a2.gno b/gnovm/tests/files/types/runtime_a2.gno index 0c1b8453591..80e7a03f133 100644 --- a/gnovm/tests/files/types/runtime_a2.gno +++ b/gnovm/tests/files/types/runtime_a2.gno @@ -2,7 +2,7 @@ package main import "fmt" -func gen() interface{} { +func gen() any { return false } diff --git a/gnovm/tests/files/types/runtime_a3.gno b/gnovm/tests/files/types/runtime_a3.gno index 4e319021f33..39b96c0f0c2 100644 --- a/gnovm/tests/files/types/runtime_a3.gno +++ b/gnovm/tests/files/types/runtime_a3.gno @@ -1,6 +1,6 @@ package main -func gen() interface{} { +func gen() any { return false } diff --git a/gnovm/tests/files/types/shift_b3.gno b/gnovm/tests/files/types/shift_b3.gno index 064f97c85a4..420092ecf5f 100644 --- a/gnovm/tests/files/types/shift_b3.gno +++ b/gnovm/tests/files/types/shift_b3.gno @@ -29,4 +29,4 @@ func main() { // Output: // bar -// uint64 +// main.U64 diff --git a/gnovm/tests/files/types/shift_b4.gno b/gnovm/tests/files/types/shift_b4.gno index 274b00fdd3a..171b4c2237d 100644 --- a/gnovm/tests/files/types/shift_b4.gno +++ b/gnovm/tests/files/types/shift_b4.gno @@ -29,4 +29,4 @@ func main() { // Output: // bar -// uint64 +// main.U64 diff --git a/gnovm/tests/files/types/shift_b5.gno b/gnovm/tests/files/types/shift_b5.gno index 27bd23c22df..a3b31c03ed8 100644 --- a/gnovm/tests/files/types/shift_b5.gno +++ b/gnovm/tests/files/types/shift_b5.gno @@ -28,4 +28,4 @@ func main() { // Output: // bar -// uint64 +// main.U64 diff --git a/gnovm/tests/files/types/shift_b6a.gno b/gnovm/tests/files/types/shift_b6a.gno index 3b1cb399dcf..56361b1a59d 100644 --- a/gnovm/tests/files/types/shift_b6a.gno +++ b/gnovm/tests/files/types/shift_b6a.gno @@ -28,4 +28,4 @@ func main() { // Output: // bar -// uint64 +// main.U64 diff --git a/gnovm/tests/files/types/shift_d43.gno b/gnovm/tests/files/types/shift_d43.gno index e9b0032ac9a..2f41fa42675 100644 --- a/gnovm/tests/files/types/shift_d43.gno +++ b/gnovm/tests/files/types/shift_d43.gno @@ -2,7 +2,7 @@ package main func main() { a := 2 - s := map[string]interface{}{"k": 1 << a} + s := map[string]any{"k": 1 << a} println(s["k"]) } diff --git a/gnovm/tests/files/types/typed_nil_a.gno b/gnovm/tests/files/types/typed_nil_a.gno new file mode 100644 index 00000000000..f212c677bab --- /dev/null +++ b/gnovm/tests/files/types/typed_nil_a.gno @@ -0,0 +1,18 @@ +package main + +import "fmt" + +type integer int + +func main() { + // illegal conversion + // should not work + if integer(nil) == nil { + fmt.Println("integer is nil") + } else { + fmt.Println("integer is not nil") + } +} + +// Error: +// main/files/types/typed_nil_a.gno:10:5: cannot convert (const (undefined)) to IntKind diff --git a/gnovm/tests/files/types/typed_nil_b.gno b/gnovm/tests/files/types/typed_nil_b.gno new file mode 100644 index 00000000000..844e5cdcd28 --- /dev/null +++ b/gnovm/tests/files/types/typed_nil_b.gno @@ -0,0 +1,16 @@ +package main + +import "fmt" + +type integer *int + +func main() { + println(integer(nil)) + fmt.Println(integer(nil)) + fmt.Printf("%T\n", integer(nil)) +} + +// Output: +// (nil main.integer) +// +// main.integer diff --git a/gnovm/tests/files/types/typed_nil_c.gno b/gnovm/tests/files/types/typed_nil_c.gno new file mode 100644 index 00000000000..5a9b1de4d5d --- /dev/null +++ b/gnovm/tests/files/types/typed_nil_c.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + println(any(nil) == (*int)(nil)) + fmt.Printf("%T\n", any(nil)) +} + +// Output: +// false +// diff --git a/gnovm/tests/files/var24.gno b/gnovm/tests/files/var24.gno index ddfa82a1e4e..f58b57a8ee7 100644 --- a/gnovm/tests/files/var24.gno +++ b/gnovm/tests/files/var24.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} = 1 + var i any = 1 var a, b, c = i.(int) } diff --git a/gnovm/tests/files/var27.gno b/gnovm/tests/files/var27.gno new file mode 100644 index 00000000000..65ab9307b9b --- /dev/null +++ b/gnovm/tests/files/var27.gno @@ -0,0 +1,80 @@ +package main + +func main() {} + +var myDep string + +var myVar1 = func() { + a := myDep1 +} + +var myDep1 string + +var myVar2 = func() { + aaa := "" + + switch myDep { + case aaa: + println(myDep2) + } +} + +var myDep2 string + +var myVar3 = func() { + for _, c := range myDep3 { + println(c) + } +} + +var myDep3 string + +var v1 = func() int { + v2 := 11 + return v2 +}() + +var v2 = func() int { + return v1 +}() + +var v3 = func() int { + return func() int { + v4 := 11 + return v4 + }() +}() + +var v4 = func() int { + return v3 +}() + +var v5 = func() int { + v6 := 11 + return func() int { + return v6 + }() +}() + +var v6 = func() int { + return v5 +}() + +var other = func() { + if true { + something := 2 + print(something) // 2 + } else { + print(something) // a string, but single shared 'st' masks the outer/global reference. + } +} +var something = "a string" + +var other1 = func() { + if true { + something1 := 2 + print(something1) // 2 + } + print(something1) // a string, but single shared 'st' masks the outer/global reference. +} +var something1 = "a string" \ No newline at end of file diff --git a/gnovm/tests/files/var28.gno b/gnovm/tests/files/var28.gno new file mode 100644 index 00000000000..279f60168a4 --- /dev/null +++ b/gnovm/tests/files/var28.gno @@ -0,0 +1,18 @@ +package main + +func main() {} + +var foo = func() (bool, bool) { + return true, true +} + +var x = func() bool { return a }() +var a, b = foo() + +var a1 = func() int { + type B1 b1 + x := B1(1) + return int(x) +} + +type b1 int \ No newline at end of file diff --git a/gnovm/tests/files/vardecl.gno b/gnovm/tests/files/vardecl.gno index 34390f26a6a..aa72475cfc9 100644 --- a/gnovm/tests/files/vardecl.gno +++ b/gnovm/tests/files/vardecl.gno @@ -1,7 +1,7 @@ package main func main() { - var i interface{} = 1 + var i any = 1 var a, ok = i.(int) println(a, ok) diff --git a/gnovm/tests/files/variadic9.gno b/gnovm/tests/files/variadic9.gno index 2f903be5168..1d9f35ae586 100644 --- a/gnovm/tests/files/variadic9.gno +++ b/gnovm/tests/files/variadic9.gno @@ -2,7 +2,7 @@ package main import "fmt" -func Sprintf(format string, a ...interface{}) string { +func Sprintf(format string, a ...any) string { return fmt.Sprintf(format, a...) } diff --git a/gnovm/tests/files/xfactor_long.gno b/gnovm/tests/files/xfactor_long.gno index edc81ba0308..201a227d16c 100644 --- a/gnovm/tests/files/xfactor_long.gno +++ b/gnovm/tests/files/xfactor_long.gno @@ -29,7 +29,7 @@ func main() { // Check if the odd number is a divisor of n temp.Mod(n, i) if temp.Sign() == 0 { - fmt.Println(i) + fmt.Println(i.String()) break } diff --git a/gnovm/tests/files/zpersist_valids.gno b/gnovm/tests/files/zpersist_valids.gno index 2709a243adf..e6400ff45da 100644 --- a/gnovm/tests/files/zpersist_valids.gno +++ b/gnovm/tests/files/zpersist_valids.gno @@ -8,7 +8,6 @@ type myStruct struct { var ( // Native types - abigint bigint = 16 abool bool = true abyte byte = 0x16 afloat32 float32 = 16.16 @@ -27,7 +26,7 @@ var ( auint16 uint16 = 16 auint32 uint32 = 16 auint64 uint64 = 16 - ainterface interface{} = struct{ a float32 }{16.0} + ainterface any = struct{ a float32 }{16.0} afunc func() string = func() string { return "A" } // TODO: @@ -52,7 +51,6 @@ func main() { } func mutateVars(stringModifier string) { - abigint *= 2 abool = !abool abyte *= 2 afloat32 *= 2 @@ -82,7 +80,6 @@ func mutateVars(stringModifier string) { func printVars(phase string) { println(phase, // variables - abigint, abool, abyte, afloat32, @@ -108,7 +105,7 @@ func printVars(phase string) { } // Output: -// preinit 16 true 22 16.16 16.16 16 16 16 16 16 97 hello slice[("A" string)] A (struct{(16 int),("A" string)} gno.land/r/demo/tests_test.myStruct) 16 16 16 16 16 struct{(16 float32)} A -// postinit 32 false 44 32.32 32.32 32 32 32 32 32 66 helloB slice[("A" string),("B" string)] A (struct{(32 int),("B" string)} gno.land/r/demo/tests_test.myStruct) 32 32 32 32 32 struct{("B" string)} B -// premain 32 false 44 32.32 32.32 32 32 32 32 32 66 helloB slice[("A" string),("B" string)] A (struct{(32 int),("B" string)} gno.land/r/demo/tests_test.myStruct) 32 32 32 32 32 struct{("B" string)} B -// postmain 64 true 88 64.64 64.64 64 64 64 64 64 67 helloBC slice[("A" string),("B" string),("C" string)] A (struct{(64 int),("C" string)} gno.land/r/demo/tests_test.myStruct) 64 64 64 64 64 struct{("C" string)} C +// preinit true 22 16.16 16.16 16 16 16 16 16 97 hello slice[("A" string)] A (struct{(16 int),("A" string)} gno.land/r/demo/tests_test.myStruct) 16 16 16 16 16 struct{(16 float32)} A +// postinit false 44 32.32 32.32 32 32 32 32 32 66 helloB slice[("A" string),("B" string)] A (struct{(32 int),("B" string)} gno.land/r/demo/tests_test.myStruct) 32 32 32 32 32 struct{("B" string)} B +// premain false 44 32.32 32.32 32 32 32 32 32 66 helloB slice[("A" string),("B" string)] A (struct{(32 int),("B" string)} gno.land/r/demo/tests_test.myStruct) 32 32 32 32 32 struct{("B" string)} B +// postmain true 88 64.64 64.64 64 64 64 64 64 67 helloBC slice[("A" string),("B" string),("C" string)] A (struct{(64 int),("C" string)} gno.land/r/demo/tests_test.myStruct) 64 64 64 64 64 struct{("C" string)} C diff --git a/gnovm/tests/files/zrealm0.gno b/gnovm/tests/files/zrealm0.gno index d3bf0e79223..b8881bac413 100644 --- a/gnovm/tests/files/zrealm0.gno +++ b/gnovm/tests/files/zrealm0.gno @@ -1,7 +1,7 @@ // PKGPATH: gno.land/r/test package test -var root interface{} +var root any func main() { println(root) diff --git a/gnovm/tests/files/zrealm1.gno b/gnovm/tests/files/zrealm1.gno index eef42cc9378..a9205004798 100644 --- a/gnovm/tests/files/zrealm1.gno +++ b/gnovm/tests/files/zrealm1.gno @@ -4,8 +4,8 @@ package test var root Node type ( - Node interface{} - Key interface{} + Node any + Key any ) type InnerNode struct { diff --git a/gnovm/tests/files/zrealm14.gno b/gnovm/tests/files/zrealm14.gno index ffffa4883fd..84ef45982cb 100644 --- a/gnovm/tests/files/zrealm14.gno +++ b/gnovm/tests/files/zrealm14.gno @@ -65,86 +65,6 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.A" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "c" -// } -// } -// ], -// "ObjectInfo": { -// "Hash": "c22ccb7832b422c83fec9943b751cb134fcbed0b", -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", -// "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", -// "RefCount": "0" -// } -// } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.A" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "d" -// } -// } -// ], -// "ObjectInfo": { -// "Hash": "86c916fd78da57d354cb38019923bf64c1a3471c", -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", -// "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", -// "RefCount": "0" -// } -// } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ // "ObjectInfo": { // "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", diff --git a/gnovm/tests/files/zrealm15.gno b/gnovm/tests/files/zrealm15.gno index 4ca6ef3b03d..b8ba84bad04 100644 --- a/gnovm/tests/files/zrealm15.gno +++ b/gnovm/tests/files/zrealm15.gno @@ -69,55 +69,6 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.A" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.S" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" -// }, -// "Index": "0", -// "TV": null -// } -// } -// ], -// "ObjectInfo": { -// "Hash": "5bf603ab337f9f40f8b22441562319d67be402b2", -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", -// "ModTime": "0", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", -// "RefCount": "0" -// } -// } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ // "Fields": [ // { diff --git a/gnovm/tests/files/zrealm2.gno b/gnovm/tests/files/zrealm2.gno index 57cd8bee349..ade5075f454 100644 --- a/gnovm/tests/files/zrealm2.gno +++ b/gnovm/tests/files/zrealm2.gno @@ -4,8 +4,8 @@ package test var root Node type ( - Node interface{} - Key interface{} + Node any + Key any ) type InnerNode struct { diff --git a/gnovm/tests/files/zrealm3.gno b/gnovm/tests/files/zrealm3.gno index cc9317c32fd..16aa6f05799 100644 --- a/gnovm/tests/files/zrealm3.gno +++ b/gnovm/tests/files/zrealm3.gno @@ -3,7 +3,7 @@ package test var root *Node -type Key interface{} +type Key any type Node struct { Key Key diff --git a/gnovm/tests/files/zrealm_crossrealm11.gno b/gnovm/tests/files/zrealm_crossrealm11.gno index 5936743ddc6..29295e99d10 100644 --- a/gnovm/tests/files/zrealm_crossrealm11.gno +++ b/gnovm/tests/files/zrealm_crossrealm11.gno @@ -9,8 +9,8 @@ import ( rtests "gno.land/r/demo/tests" ) -func getPrevRealm() std.Realm { - return std.PrevRealm() +func getPreviousRealm() std.Realm { + return std.PreviousRealm() } func Exec(fn func()) { @@ -29,7 +29,7 @@ func main() { } assertRealm := func(r std.Realm) { - pkgPath := callersByAddr[r.Addr()] + pkgPath := callersByAddr[r.Address()] if r.IsUser() && pkgPath != "user1.gno" { panic(ufmt.Sprintf("ERROR: expected: 'user1.gno', got:'%s'", pkgPath)) } else if !r.IsUser() && pkgPath != r.PkgPath() { @@ -43,51 +43,51 @@ func main() { }{ { callStackAdd: "", - callerFn: std.PrevRealm, + callerFn: std.PreviousRealm, }, { - callStackAdd: " -> r/crossrealm_test.getPrevRealm", - callerFn: getPrevRealm, + callStackAdd: " -> r/crossrealm_test.getPreviousRealm", + callerFn: getPreviousRealm, }, { callStackAdd: " -> p/demo/tests", - callerFn: ptests.GetPrevRealm, + callerFn: ptests.GetPreviousRealm, }, { callStackAdd: " -> p/demo/tests -> p/demo/tests/subtests", - callerFn: ptests.GetPSubtestsPrevRealm, + callerFn: ptests.GetPSubtestsPreviousRealm, }, { callStackAdd: " -> r/demo/tests", - callerFn: rtests.GetPrevRealm, + callerFn: rtests.GetPreviousRealm, }, { callStackAdd: " -> r/demo/tests -> r/demo/tests/subtests", - callerFn: rtests.GetRSubtestsPrevRealm, + callerFn: rtests.GetRSubtestsPreviousRealm, }, } println("---") // needed to have space prefixes - printColumns("STACK", "std.PrevRealm") + printColumns("STACK", "std.PreviousRealm") printColumns("-----", "------------------") baseCallStack := "user1.gno -> r/crossrealm_test.main" for i, tt := range tests { - printColumns(baseCallStack+tt.callStackAdd, callersByAddr[tt.callerFn().Addr()]) + printColumns(baseCallStack+tt.callStackAdd, callersByAddr[tt.callerFn().Address()]) Exec(func() { r := tt.callerFn() assertRealm(r) - printColumns(baseCallStack+" -> r/crossrealm_test.Exec"+tt.callStackAdd, callersByAddr[r.Addr()]) + printColumns(baseCallStack+" -> r/crossrealm_test.Exec"+tt.callStackAdd, callersByAddr[r.Address()]) }) rtests.Exec(func() { r := tt.callerFn() assertRealm(r) - printColumns(baseCallStack+" -> r/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Addr()]) + printColumns(baseCallStack+" -> r/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Address()]) }) ptests.Exec(func() { r := tt.callerFn() assertRealm(r) - printColumns(baseCallStack+" -> p/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Addr()]) + printColumns(baseCallStack+" -> p/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.Address()]) }) } } @@ -111,16 +111,16 @@ func printColumns(left, right string) { // Output: // --- -// STACK = std.PrevRealm +// STACK = std.PreviousRealm // ----- = ------------------ // user1.gno -> r/crossrealm_test.main = user1.gno // user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec = user1.gno // user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec = gno.land/r/demo/tests // user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.getPrevRealm = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/crossrealm_test.getPrevRealm = user1.gno -// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/crossrealm_test.getPrevRealm = gno.land/r/demo/tests -// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/crossrealm_test.getPrevRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.getPreviousRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/crossrealm_test.getPreviousRealm = user1.gno +// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/crossrealm_test.getPreviousRealm = gno.land/r/demo/tests +// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/crossrealm_test.getPreviousRealm = user1.gno // user1.gno -> r/crossrealm_test.main -> p/demo/tests = user1.gno // user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> p/demo/tests = user1.gno // user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> p/demo/tests = gno.land/r/demo/tests diff --git a/gnovm/tests/files/zrealm_crossrealm12.gno b/gnovm/tests/files/zrealm_crossrealm12.gno index f2f229cd5de..5ae4d69879a 100644 --- a/gnovm/tests/files/zrealm_crossrealm12.gno +++ b/gnovm/tests/files/zrealm_crossrealm12.gno @@ -21,13 +21,13 @@ func main() { for _, test := range tests { r := test.fn() - if std.DerivePkgAddr(r.PkgPath()) != r.Addr() { + if std.DerivePkgAddr(r.PkgPath()) != r.Address() { panic(fmt.Sprintf("ERROR: expected: %v, got: %v", - std.DerivePkgAddr(r.PkgPath()), r.Addr(), + std.DerivePkgAddr(r.PkgPath()), r.Address(), )) } - println(r.PkgPath(), r.Addr()) + println(r.PkgPath(), r.Address()) } } diff --git a/gnovm/tests/files/zrealm_crossrealm13.gno b/gnovm/tests/files/zrealm_crossrealm13.gno index 4daeb6de366..765fe4d0489 100644 --- a/gnovm/tests/files/zrealm_crossrealm13.gno +++ b/gnovm/tests/files/zrealm_crossrealm13.gno @@ -7,15 +7,15 @@ import ( func main() { PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) std.TestSetRealm(std.NewUserRealm("g1user")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) - std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) + println(pad("PreviousRealm:"), std.PreviousRealm()) + std.TestSetRealm(std.NewCodeRealm("gno.land/r/sys/users")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) } func pad(s string) string { @@ -27,7 +27,7 @@ func pad(s string) string { func PrintRealm() { println(pad("PrintRealm: CurrentRealm:"), std.CurrentRealm()) - println(pad("PrintRealm: PrevRealm:"), std.PrevRealm()) + println(pad("PrintRealm: PreviousRealm:"), std.PreviousRealm()) } // Because this is the context of a package, using PrintRealm() @@ -35,14 +35,14 @@ func PrintRealm() { // Output: // PrintRealm: CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) -// PrintRealm: CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) -// CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: CurrentRealm: (struct{("g1njxh4leja7h52ea0lnq9crx3j6782g77nc7yd4" std.Address),("gno.land/r/sys/users" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// CurrentRealm: (struct{("g1njxh4leja7h52ea0lnq9crx3j6782g77nc7yd4" std.Address),("gno.land/r/sys/users" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) diff --git a/gnovm/tests/files/zrealm_crossrealm13a.gno b/gnovm/tests/files/zrealm_crossrealm13a.gno index 2fc37804fce..fdf4bd75ba8 100644 --- a/gnovm/tests/files/zrealm_crossrealm13a.gno +++ b/gnovm/tests/files/zrealm_crossrealm13a.gno @@ -8,15 +8,15 @@ import ( func main() { PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) std.TestSetRealm(std.NewUserRealm("g1user")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) - std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) + println(pad("PreviousRealm:"), std.PreviousRealm()) + std.TestSetRealm(std.NewCodeRealm("gno.land/r/sys/users")) PrintRealm() println(pad("CurrentRealm:"), std.CurrentRealm()) - println(pad("PrevRealm:"), std.PrevRealm()) + println(pad("PreviousRealm:"), std.PreviousRealm()) } func pad(s string) string { @@ -28,19 +28,19 @@ func pad(s string) string { func PrintRealm() { println(pad("PrintRealm: CurrentRealm:"), std.CurrentRealm()) - println(pad("PrintRealm: PrevRealm:"), std.PrevRealm()) + println(pad("PrintRealm: PreviousRealm:"), std.PreviousRealm()) } // Output: // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g1user" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1user" std.Address),("" string)} std.Realm) // CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) // PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) -// PrintRealm: PrevRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) -// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PreviousRealm: (struct{("g1njxh4leja7h52ea0lnq9crx3j6782g77nc7yd4" std.Address),("gno.land/r/sys/users" string)} std.Realm) +// CurrentRealm: (struct{("g1njxh4leja7h52ea0lnq9crx3j6782g77nc7yd4" std.Address),("gno.land/r/sys/users" string)} std.Realm) +// PreviousRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) diff --git a/gnovm/tests/files/zrealm_initctx.gno b/gnovm/tests/files/zrealm_initctx.gno index 2fda65e7681..4fc05a6e59c 100644 --- a/gnovm/tests/files/zrealm_initctx.gno +++ b/gnovm/tests/files/zrealm_initctx.gno @@ -10,8 +10,8 @@ var addr = std.Address("test") var addrInit = std.Address("addrInit") func init() { - addr = std.GetOrigCaller() - addrInit = tests.InitOrigCaller() + addr = std.OriginCaller() + addrInit = tests.InitOriginCaller() } func main() { diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index 6f8045107dc..37178cf4ea7 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -5,19 +5,19 @@ import ( "std" ) -var node interface{} +var node any func init() { - node = std.GetHeight + node = std.ChainHeight } func main() { - // NOTE: this test uses GetHeight and GetChainID, which are "pure" + // NOTE: this test uses ChainHeight and ChainID, which are "pure" // natively bound functions (ie. not indirections through a wrapper fn, // to convert the types to builtin go/gno identifiers). f := node.(func() int64) println(f()) - node = std.GetChainID + node = std.ChainID g := node.(func() string) println(g()) } diff --git a/gnovm/tests/files/zrealm_natbind1_stdlibs.gno b/gnovm/tests/files/zrealm_natbind1_stdlibs.gno index f44b6ab4fcf..2011624b186 100644 --- a/gnovm/tests/files/zrealm_natbind1_stdlibs.gno +++ b/gnovm/tests/files/zrealm_natbind1_stdlibs.gno @@ -6,7 +6,7 @@ import ( ) func main() { - println(std.GetChainDomain()) + println(std.ChainDomain()) } // Output: diff --git a/gnovm/tests/files/zrealm_std0.gno b/gnovm/tests/files/zrealm_std0.gno index 3f6bdae2537..fff6d6882b8 100644 --- a/gnovm/tests/files/zrealm_std0.gno +++ b/gnovm/tests/files/zrealm_std0.gno @@ -6,7 +6,7 @@ import ( ) func main() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() println(caller) } diff --git a/gnovm/tests/files/zrealm_std1.gno b/gnovm/tests/files/zrealm_std1.gno index d75a2c60b71..8206b7b4097 100644 --- a/gnovm/tests/files/zrealm_std1.gno +++ b/gnovm/tests/files/zrealm_std1.gno @@ -8,14 +8,14 @@ import ( var aset *std.AddressList func init() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() aset = std.NewAddressList() aset.AddAddress(caller) } func main() { println(*aset) - caller := std.GetOrigCaller() + caller := std.OriginCaller() err := aset.AddAddress(caller) println("error:", err) has := aset.HasAddress(caller) diff --git a/gnovm/tests/files/zrealm_std2.gno b/gnovm/tests/files/zrealm_std2.gno index 810210c6160..15d137bf556 100644 --- a/gnovm/tests/files/zrealm_std2.gno +++ b/gnovm/tests/files/zrealm_std2.gno @@ -9,14 +9,14 @@ import ( var aset std.AddressSet func init() { - caller := std.GetOrigCaller() + caller := std.OriginCaller() aset = std.NewAddressList() aset.AddAddress(caller) } func main() { println(*(aset.(*std.AddressList))) - caller := std.GetOrigCaller() + caller := std.OriginCaller() err := aset.AddAddress(caller) println("error:", err) has := aset.HasAddress(caller) diff --git a/gnovm/tests/files/zrealm_std6.gno b/gnovm/tests/files/zrealm_std6.gno index 17a79c1d43b..b2ab6432d13 100644 --- a/gnovm/tests/files/zrealm_std6.gno +++ b/gnovm/tests/files/zrealm_std6.gno @@ -6,7 +6,7 @@ import ( ) var ( - realmAddr std.Address = std.GetOrigPkgAddr() + realmAddr std.Address = std.OriginPkgAddress() ) func main() { diff --git a/gnovm/tests/integ/context/context.gno b/gnovm/tests/integ/context/context.gno index 92a9cc632b7..58274b8e5d4 100644 --- a/gnovm/tests/integ/context/context.gno +++ b/gnovm/tests/integ/context/context.gno @@ -7,5 +7,5 @@ import ( func Context() { // This requires a Context to work; it will fail ugly if the Context isn't available. - fmt.Printf("Context worked: %d\n", std.GetHeight()) + fmt.Printf("Context worked: %d\n", std.ChainHeight()) } diff --git a/gnovm/tests/integ/init/gno.mod b/gnovm/tests/integ/init/gno.mod new file mode 100644 index 00000000000..28c7e51b750 --- /dev/null +++ b/gnovm/tests/integ/init/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/init diff --git a/gnovm/tests/integ/init/main.gno b/gnovm/tests/integ/init/main.gno new file mode 100644 index 00000000000..88cfafb9f24 --- /dev/null +++ b/gnovm/tests/integ/init/main.gno @@ -0,0 +1,10 @@ +package main + +var _ = func() int { + println("HELLO HELLO!!") + return 1 +}() + +func init() { + println("HELLO WORLD!") +} diff --git a/gnovm/tests/integ/replace_with_module/gno.mod b/gnovm/tests/integ/replace_with_module/gno.mod index de730c90a53..2098881e406 100644 --- a/gnovm/tests/integ/replace_with_module/gno.mod +++ b/gnovm/tests/integ/replace_with_module/gno.mod @@ -1,5 +1,5 @@ module gno.land/tests/replaceavl replace ( - "gno.land/p/demo/avl" => "gno.land/p/demo/users" + "gno.land/p/demo/avl" => "gno.land/p/demo/seqid" ) diff --git a/gnovm/tests/integ/replace_with_module/main.gno b/gnovm/tests/integ/replace_with_module/main.gno index 7f78497fa02..89d9286942c 100644 --- a/gnovm/tests/integ/replace_with_module/main.gno +++ b/gnovm/tests/integ/replace_with_module/main.gno @@ -1,7 +1,7 @@ package main import ( - "gno.land/p/demo/avl" + avl "gno.land/p/demo/avl" ) -var foo = avl.Bar +var foo = avl.ID(123) diff --git a/gnovm/tests/stdlibs/README.md b/gnovm/tests/stdlibs/README.md index 16d5d171342..d264cf35c45 100644 --- a/gnovm/tests/stdlibs/README.md +++ b/gnovm/tests/stdlibs/README.md @@ -4,3 +4,5 @@ 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. + +Adding imports that don't exist in the corresponding normal stdlib is undefined behavior diff --git a/gnovm/tests/stdlibs/fmt/doc.gno b/gnovm/tests/stdlibs/fmt/doc.gno new file mode 100644 index 00000000000..0ce509a8809 --- /dev/null +++ b/gnovm/tests/stdlibs/fmt/doc.gno @@ -0,0 +1,380 @@ +// 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 fmt implements formatted I/O with functions analogous +to C's printf and scanf. The format 'verbs' are derived from C's but +are simpler. + +# Printing + +The verbs: + +General: + + %v the value in a default format + when printing structs, the plus flag (%+v) adds field names + %#v a Go-syntax representation of the value + (floating-point infinities and NaNs print as ±Inf and NaN) + %T a Go-syntax representation of the type of the value + %% a literal percent sign; consumes no value + +Boolean: + + %t the word true or false + +Integer: + + %b base 2 + %c the character represented by the corresponding Unicode code point + %d base 10 + %o base 8 + %O base 8 with 0o prefix + %q a single-quoted character literal safely escaped with Go syntax. + %x base 16, with lower-case letters for a-f + %X base 16, with upper-case letters for A-F + %U Unicode format: U+1234; same as "U+%04X" + +Floating-point constituents: + + %b decimalless scientific notation with exponent a power of two, + in the manner of strconv.FormatFloat with the 'b' format, + e.g. -123456p-78 + %e scientific notation, e.g. -1.234456e+78 + %E scientific notation, e.g. -1.234456E+78 + %f decimal point but no exponent, e.g. 123.456 + %F synonym for %f + %g %e for large exponents, %f otherwise. Precision is discussed below. + %G %E for large exponents, %F otherwise + %x hexadecimal notation (with decimal power of two exponent), e.g. -0x1.23abcp+20 + %X upper-case hexadecimal notation, e.g. -0X1.23ABCP+20 + +String and slice of bytes (treated equivalently with these verbs): + + %s the uninterpreted bytes of the string or slice + %q a double-quoted string safely escaped with Go syntax + %x base 16, lower-case, two characters per byte + %X base 16, upper-case, two characters per byte + +Slice: + + %p address of 0th element in base 16 notation, with leading 0x + +Pointer: + + %p base 16 notation, with leading 0x + The %b, %d, %o, %x and %X verbs also work with pointers, + formatting the value exactly as if it were an integer. + +The default format for %v is: + + bool: %t + int, int8 etc.: %d + uint, uint8 etc.: %d, %#x if printed with %#v + float32, etc: %g + string: %s + chan: %p + pointer: %p + +For compound objects, the elements are printed using these rules, recursively, +laid out like this: + + struct: {field0 field1 ...} + array, slice: [elem0 elem1 ...] + maps: map[key1:value1 key2:value2 ...] + pointer to above: &{}, &[], &map[] + +Width is specified by an optional decimal number immediately preceding the verb. +If absent, the width is whatever is necessary to represent the value. +Precision is specified after the (optional) width by a period followed by a +decimal number. If no period is present, a default precision is used. +A period with no following number specifies a precision of zero. +Examples: + + %f default width, default precision + %9f width 9, default precision + %.2f default width, precision 2 + %9.2f width 9, precision 2 + %9.f width 9, precision 0 + +Width and precision are measured in units of Unicode code points, +that is, runes. (This differs from C's printf where the +units are always measured in bytes.) Either or both of the flags +may be replaced with the character '*', causing their values to be +obtained from the next operand (preceding the one to format), +which must be of type int. + +For most values, width is the minimum number of runes to output, +padding the formatted form with spaces if necessary. + +For strings, byte slices and byte arrays, however, precision +limits the length of the input to be formatted (not the size of +the output), truncating if necessary. Normally it is measured in +runes, but for these types when formatted with the %x or %X format +it is measured in bytes. + +For floating-point values, width sets the minimum width of the field and +precision sets the number of places after the decimal, if appropriate, +except that for %g/%G precision sets the maximum number of significant +digits (trailing zeros are removed). For example, given 12.345 the format +%6.3f prints 12.345 while %.3g prints 12.3. The default precision for %e, %f +and %#g is 6; for %g it is the smallest number of digits necessary to identify +the value uniquely. + +When formatting a single integer code point or a rune string (type []rune) +with %q, invalid Unicode code points are changed to the Unicode replacement +character, U+FFFD, as in [strconv.QuoteRune]. + +Other flags: + + '+' always print a sign for numeric values; + guarantee ASCII-only output for %q (%+q) + '-' pad with spaces on the right rather than the left (left-justify the field) + '#' alternate format: add leading 0b for binary (%#b), 0 for octal (%#o), + 0x or 0X for hex (%#x or %#X); suppress 0x for %p (%#p); + for %q, print a raw (backquoted) string if [strconv.CanBackquote] + returns true; + always print a decimal point for %e, %E, %f, %F, %g and %G; + do not remove trailing zeros for %g and %G; + write e.g. U+0078 'x' if the character is printable for %U (%#U) + ' ' (space) leave a space for elided sign in numbers (% d); + put spaces between bytes printing strings or slices in hex (% x, % X) + '0' pad with leading zeros rather than spaces; + for numbers, this moves the padding after the sign + +Flags are ignored by verbs that do not expect them. +For example there is no alternate decimal format, so %#d and %d +behave identically. + +For each Printf-like function, there is also a Print function +that takes no format and is equivalent to saying %v for every +operand. Another variant Println inserts blanks between +operands and appends a newline. + +Regardless of the verb, if an operand is an interface value, +the internal concrete value is used, not the interface itself. +Thus: + + var i interface{} = 23 + fmt.Printf("%v\n", i) + +will print 23. + +Except when printed using the verbs %T and %p, special +formatting considerations apply for operands that implement +certain interfaces. In order of application: + +1. If the operand is a [reflect.Value], the operand is replaced by the +concrete value that it holds, and printing continues with the next rule. + +2. If an operand implements the [Formatter] interface, it will +be invoked. In this case the interpretation of verbs and flags is +controlled by that implementation. + +3. If the %v verb is used with the # flag (%#v) and the operand +implements the [GoStringer] interface, that will be invoked. + +If the format (which is implicitly %v for [Println] etc.) is valid +for a string (%s %q %x %X), or is %v but not %#v, +the following two rules apply: + +4. If an operand implements the error interface, the Error method +will be invoked to convert the object to a string, which will then +be formatted as required by the verb (if any). + +5. If an operand implements method String() string, that method +will be invoked to convert the object to a string, which will then +be formatted as required by the verb (if any). + +For compound operands such as slices and structs, the format +applies to the elements of each operand, recursively, not to the +operand as a whole. Thus %q will quote each element of a slice +of strings, and %6.2f will control formatting for each element +of a floating-point array. + +However, when printing a byte slice with a string-like verb +(%s %q %x %X), it is treated identically to a string, as a single item. + +To avoid recursion in cases such as + + type X string + func (x X) String() string { return Sprintf("<%s>", x) } + +convert the value before recurring: + + func (x X) String() string { return Sprintf("<%s>", string(x)) } + +Infinite recursion can also be triggered by self-referential data +structures, such as a slice that contains itself as an element, if +that type has a String method. Such pathologies are rare, however, +and the package does not protect against them. + +When printing a struct, fmt cannot and therefore does not invoke +formatting methods such as Error or String on unexported fields. + +# Explicit argument indexes + +In [Printf], [Sprintf], and [Fprintf], the default behavior is for each +formatting verb to format successive arguments passed in the call. +However, the notation [n] immediately before the verb indicates that the +nth one-indexed argument is to be formatted instead. The same notation +before a '*' for a width or precision selects the argument index holding +the value. After processing a bracketed expression [n], subsequent verbs +will use arguments n+1, n+2, etc. unless otherwise directed. + +For example, + + fmt.Sprintf("%[2]d %[1]d\n", 11, 22) + +will yield "22 11", while + + fmt.Sprintf("%[3]*.[2]*[1]f", 12.0, 2, 6) + +equivalent to + + fmt.Sprintf("%6.2f", 12.0) + +will yield " 12.00". Because an explicit index affects subsequent verbs, +this notation can be used to print the same values multiple times +by resetting the index for the first argument to be repeated: + + fmt.Sprintf("%d %d %#[1]x %#x", 16, 17) + +will yield "16 17 0x10 0x11". + +# Format errors + +If an invalid argument is given for a verb, such as providing +a string to %d, the generated string will contain a +description of the problem, as in these examples: + + Wrong type or unknown verb: %!verb(type=value) + Printf("%d", "hi"): %!d(string=hi) + Too many arguments: %!(EXTRA type=value) + Printf("hi", "guys"): hi%!(EXTRA string=guys) + Too few arguments: %!verb(MISSING) + Printf("hi%d"): hi%!d(MISSING) + Non-int for width or precision: %!(BADWIDTH) or %!(BADPREC) + Printf("%*s", 4.5, "hi"): %!(BADWIDTH)hi + Printf("%.*s", 4.5, "hi"): %!(BADPREC)hi + Invalid or invalid use of argument index: %!(BADINDEX) + Printf("%*[2]d", 7): %!d(BADINDEX) + Printf("%.[2]d", 7): %!d(BADINDEX) + +All errors begin with the string "%!" followed sometimes +by a single character (the verb) and end with a parenthesized +description. + +If an Error or String method triggers a panic when called by a +print routine, the fmt package reformats the error message +from the panic, decorating it with an indication that it came +through the fmt package. For example, if a String method +calls panic("bad"), the resulting formatted message will look +like + + %!s(PANIC=bad) + +The %!s just shows the print verb in use when the failure +occurred. If the panic is caused by a nil receiver to an Error +or String method, however, the output is the undecorated +string, "". + +# Scanning + +An analogous set of functions scans formatted text to yield +values. [Scan], [Scanf] and [Scanln] read from [os.Stdin]; [Fscan], +[Fscanf] and [Fscanln] read from a specified [io.Reader]; [Sscan], +[Sscanf] and [Sscanln] read from an argument string. + +[Scan], [Fscan], [Sscan] treat newlines in the input as spaces. + +[Scanln], [Fscanln] and [Sscanln] stop scanning at a newline and +require that the items be followed by a newline or EOF. + +[Scanf], [Fscanf], and [Sscanf] parse the arguments according to a +format string, analogous to that of [Printf]. In the text that +follows, 'space' means any Unicode whitespace character +except newline. + +In the format string, a verb introduced by the % character +consumes and parses input; these verbs are described in more +detail below. A character other than %, space, or newline in +the format consumes exactly that input character, which must +be present. A newline with zero or more spaces before it in +the format string consumes zero or more spaces in the input +followed by a single newline or the end of the input. A space +following a newline in the format string consumes zero or more +spaces in the input. Otherwise, any run of one or more spaces +in the format string consumes as many spaces as possible in +the input. Unless the run of spaces in the format string +appears adjacent to a newline, the run must consume at least +one space from the input or find the end of the input. + +The handling of spaces and newlines differs from that of C's +scanf family: in C, newlines are treated as any other space, +and it is never an error when a run of spaces in the format +string finds no spaces to consume in the input. + +The verbs behave analogously to those of [Printf]. +For example, %x will scan an integer as a hexadecimal number, +and %v will scan the default representation format for the value. +The [Printf] verbs %p and %T and the flags # and + are not implemented. +For floating-point values, all valid formatting verbs +(%b %e %E %f %F %g %G %x %X and %v) are equivalent and accept +both decimal and hexadecimal notation (for example: "2.3e+7", "0x4.5p-8") +and digit-separating underscores (for example: "3.14159_26535_89793"). + +Input processed by verbs is implicitly space-delimited: the +implementation of every verb except %c starts by discarding +leading spaces from the remaining input, and the %s verb +(and %v reading into a string) stops consuming input at the first +space or newline character. + +The familiar base-setting prefixes 0b (binary), 0o and 0 (octal), +and 0x (hexadecimal) are accepted when scanning integers +without a format or with the %v verb, as are digit-separating +underscores. + +Width is interpreted in the input text but there is no +syntax for scanning with a precision (no %5.2f, just %5f). +If width is provided, it applies after leading spaces are +trimmed and specifies the maximum number of runes to read +to satisfy the verb. For example, + + Sscanf(" 1234567 ", "%5s%d", &s, &i) + +will set s to "12345" and i to 67 while + + Sscanf(" 12 34 567 ", "%5s%d", &s, &i) + +will set s to "12" and i to 34. + +In all the scanning functions, a carriage return followed +immediately by a newline is treated as a plain newline +(\r\n means the same as \n). + +In all the scanning functions, if an operand implements method +[Scan] (that is, it implements the [Scanner] interface) that +method will be used to scan the text for that operand. Also, +if the number of arguments scanned is less than the number of +arguments provided, an error is returned. + +All arguments to be scanned must be either pointers to basic +types or implementations of the [Scanner] interface. + +Like [Scanf] and [Fscanf], [Sscanf] need not consume its entire input. +There is no way to recover how much of the input string [Sscanf] used. + +Note: [Fscan] etc. can read one character (rune) past the input +they return, which means that a loop calling a scan routine +may skip some of the input. This is usually a problem only +when there is no space between input values. If the reader +provided to [Fscan] implements ReadRune, that method will be used +to read characters. If the reader also implements UnreadRune, +that method will be used to save the character and successive +calls will not lose data. To attach ReadRune and UnreadRune +methods to a reader without that capability, use +[bufio.NewReader]. +*/ +package fmt diff --git a/gnovm/tests/stdlibs/fmt/errors.gno b/gnovm/tests/stdlibs/fmt/errors.gno new file mode 100644 index 00000000000..fac936409f2 --- /dev/null +++ b/gnovm/tests/stdlibs/fmt/errors.gno @@ -0,0 +1,77 @@ +// Copyright 2018 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 fmt + +import ( + "errors" + "sort" +) + +// Errorf formats according to a format specifier and returns the string as a +// value that satisfies error. +// +// If the format specifier includes a %w verb with an error operand, +// the returned error will implement an Unwrap method returning the operand. +// If there is more than one %w verb, the returned error will implement an +// Unwrap method returning a []error containing all the %w operands in the +// order they appear in the arguments. +// It is invalid to supply the %w verb with an operand that does not implement +// the error interface. The %w verb is otherwise a synonym for %v. +func Errorf(format string, a ...any) error { + p := newPrinter() + p.wrapErrs = true + p.doPrintf(format, a) + s := string(p.buf) + var err error + switch len(p.wrappedErrs) { + case 0: + err = errors.New(s) + case 1: + w := &wrapError{msg: s} + w.err, _ = a[p.wrappedErrs[0]].(error) + err = w + default: + if p.reordered { + sort.Ints(p.wrappedErrs) + } + var errs []error + for i, argNum := range p.wrappedErrs { + if i > 0 && p.wrappedErrs[i-1] == argNum { + continue + } + if e, ok := a[argNum].(error); ok { + errs = append(errs, e) + } + } + err = &wrapErrors{s, errs} + } + return err +} + +type wrapError struct { + msg string + err error +} + +func (e *wrapError) Error() string { + return e.msg +} + +func (e *wrapError) Unwrap() error { + return e.err +} + +type wrapErrors struct { + msg string + errs []error +} + +func (e *wrapErrors) Error() string { + return e.msg +} + +func (e *wrapErrors) Unwrap() []error { + return e.errs +} diff --git a/gnovm/tests/stdlibs/fmt/errors_test.gno b/gnovm/tests/stdlibs/fmt/errors_test.gno new file mode 100644 index 00000000000..9dd79a23180 --- /dev/null +++ b/gnovm/tests/stdlibs/fmt/errors_test.gno @@ -0,0 +1,126 @@ +// Copyright 2018 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 fmt_test + +import ( + "errors" + "fmt" + "testing" +) + +func TestErrorf(t *testing.T) { + // noVetErrorf is an alias for fmt.Errorf that does not trigger vet warnings for + // %w format strings. + noVetErrorf := fmt.Errorf + + wrapped := errors.New("inner error") + for _, test := range []struct { + err error + wantText string + wantUnwrap error + wantSplit []error + }{{ + err: fmt.Errorf("%w", wrapped), + wantText: "inner error", + wantUnwrap: wrapped, + }, { + err: fmt.Errorf("added context: %w", wrapped), + wantText: "added context: inner error", + wantUnwrap: wrapped, + }, { + err: fmt.Errorf("%w with added context", wrapped), + wantText: "inner error with added context", + wantUnwrap: wrapped, + }, { + err: fmt.Errorf("%s %w %v", "prefix", wrapped, "suffix"), + wantText: "prefix inner error suffix", + wantUnwrap: wrapped, + }, { + err: fmt.Errorf("%[2]s: %[1]w", wrapped, "positional verb"), + wantText: "positional verb: inner error", + wantUnwrap: wrapped, + }, { + err: fmt.Errorf("%v", wrapped), + wantText: "inner error", + }, { + err: fmt.Errorf("added context: %v", wrapped), + wantText: "added context: inner error", + }, { + err: fmt.Errorf("%v with added context", wrapped), + wantText: "inner error with added context", + }, { + err: noVetErrorf("%w is not an error", "not-an-error"), + wantText: "%!w(string=not-an-error) is not an error", + }, { + err: noVetErrorf("wrapped two errors: %w %w", errString("1"), errString("2")), + wantText: "wrapped two errors: 1 2", + wantSplit: []error{errString("1"), errString("2")}, + }, { + err: noVetErrorf("wrapped three errors: %w %w %w", errString("1"), errString("2"), errString("3")), + wantText: "wrapped three errors: 1 2 3", + wantSplit: []error{errString("1"), errString("2"), errString("3")}, + }, { + err: noVetErrorf("wrapped nil error: %w %w %w", errString("1"), nil, errString("2")), + wantText: "wrapped nil error: 1 %!w() 2", + wantSplit: []error{errString("1"), errString("2")}, + }, { + err: noVetErrorf("wrapped one non-error: %w %w %w", errString("1"), "not-an-error", errString("3")), + wantText: "wrapped one non-error: 1 %!w(string=not-an-error) 3", + wantSplit: []error{errString("1"), errString("3")}, + }, { + err: fmt.Errorf("wrapped errors out of order: %[3]w %[2]w %[1]w", errString("1"), errString("2"), errString("3")), + wantText: "wrapped errors out of order: 3 2 1", + wantSplit: []error{errString("1"), errString("2"), errString("3")}, + }, { + err: fmt.Errorf("wrapped several times: %[1]w %[1]w %[2]w %[1]w", errString("1"), errString("2")), + wantText: "wrapped several times: 1 1 2 1", + wantSplit: []error{errString("1"), errString("2")}, + }, { + err: fmt.Errorf("%w", nil), + wantText: "%!w()", + wantUnwrap: nil, // still nil + }} { + if got, want := errorUnwrap(test.err), test.wantUnwrap; got != want { + t.Errorf("Formatted error: %v\nerrors.Unwrap() = %v, want %v", test.err, got, want) + } + if got, want := splitErr(test.err), test.wantSplit; !equalErrorSlices(got, want) { + t.Errorf("Formatted error: %v\nUnwrap() []error = %v, want %v", test.err, got, want) + } + if got, want := test.err.Error(), test.wantText; got != want { + t.Errorf("err.Error() = %q, want %q", got, want) + } + } +} + +func equalErrorSlices(e1, e2 []error) bool { + if len(e1) != len(e2) { + return false + } + for i := 0; i < len(e1); i++ { + if e1[i] != e2[i] { + return false + } + } + return true +} + +// should eventually be errors.Unwrap +func errorUnwrap(e error) error { + if e, ok := e.(interface{ Unwrap() error }); ok { + return e.Unwrap() + } + return nil +} + +func splitErr(err error) []error { + if e, ok := err.(interface{ Unwrap() []error }); ok { + return e.Unwrap() + } + return nil +} + +type errString string + +func (e errString) Error() string { return string(e) } diff --git a/gnovm/tests/stdlibs/fmt/export_test.gno b/gnovm/tests/stdlibs/fmt/export_test.gno new file mode 100644 index 00000000000..85fcd8e1c9c --- /dev/null +++ b/gnovm/tests/stdlibs/fmt/export_test.gno @@ -0,0 +1,7 @@ +// Copyright 2012 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 fmt + +var Parsenum = parsenum diff --git a/gnovm/tests/stdlibs/fmt/fmt_test.gno b/gnovm/tests/stdlibs/fmt/fmt_test.gno new file mode 100644 index 00000000000..df6e527afd0 --- /dev/null +++ b/gnovm/tests/stdlibs/fmt/fmt_test.gno @@ -0,0 +1,1740 @@ +// 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 fmt_test + +import ( + "bytes" + "fmt" + "io" + "math" + "strings" + "testing" + "time" +) + +type ( + renamedBool bool + renamedInt int + renamedInt8 int8 + renamedInt16 int16 + renamedInt32 int32 + renamedInt64 int64 + renamedUint uint + renamedUint8 uint8 + renamedUint16 uint16 + renamedUint32 uint32 + renamedUint64 uint64 + renamedString string + renamedBytes []byte + renamedFloat32 float32 + renamedFloat64 float64 +) + +func TestFmtInterface(t *testing.T) { + var i1 any + i1 = "abc" + s := fmt.Sprintf("%s", i1) + if s != "abc" { + t.Errorf(`Sprintf("%%s", empty("abc")) = %q want %q`, s, "abc") + } +} + +var ( + NaN = math.NaN() + posInf = math.Inf(1) + negInf = math.Inf(-1) + + intVar = 0 + + array = [5]int{1, 2, 3, 4, 5} + iarray = [4]any{1, "hello", 2.5, nil} + slice = array[:] + islice = iarray[:] +) + +type A struct { + i int + j uint + s string + x []int +} + +type I int + +func (i I) String() string { return fmt.Sprintf("<%d>", int(i)) } + +type B struct { + I I + j int +} + +type C struct { + i int + B +} + +type F int + +func (f F) Format(s fmt.State, c rune) { + fmt.Fprintf(s, "<%c=F(%d)>", c, int(f)) +} + +type G int + +func (g G) GoString() string { + return fmt.Sprintf("GoString(%d)", int(g)) +} + +type S struct { + F F // a struct field that Formats + G G // a struct field that GoStrings +} + +type SI struct { + I any +} + +// P is a type with a String method with pointer receiver for testing %p. +type P int + +var pValue P + +func (p *P) String() string { + return "String(p)" +} + +var ( + barray = [5]renamedUint8{1, 2, 3, 4, 5} + bslice = barray[:] +) + +type byteStringer byte + +func (byteStringer) String() string { + return "X" +} + +var byteStringerSlice = []byteStringer{'h', 'e', 'l', 'l', 'o'} + +type byteFormatter byte + +func (byteFormatter) Format(f fmt.State, _ rune) { + fmt.Fprint(f, "X") +} + +var byteFormatterSlice = []byteFormatter{'h', 'e', 'l', 'l', 'o'} + +type writeStringFormatter string + +func (sf writeStringFormatter) Format(f fmt.State, c rune) { + if sw, ok := f.(io.StringWriter); ok { + sw.WriteString("***" + string(sf) + "***") + } +} + +var fmtTests = []struct { + fmt string + val any + out string +}{ + {"%d", 12345, "12345"}, + {"%v", 12345, "12345"}, + {"%t", true, "true"}, + + // basic string + {"%s", "abc", "abc"}, + {"%q", "abc", `"abc"`}, + {"%x", "abc", "616263"}, + {"%x", "\xff\xf0\x0f\xff", "fff00fff"}, + {"%X", "\xff\xf0\x0f\xff", "FFF00FFF"}, + {"%x", "", ""}, + {"% x", "", ""}, + {"%#x", "", ""}, + {"%# x", "", ""}, + {"%x", "xyz", "78797a"}, + {"%X", "xyz", "78797A"}, + {"% x", "xyz", "78 79 7a"}, + {"% X", "xyz", "78 79 7A"}, + {"%#x", "xyz", "0x78797a"}, + {"%#X", "xyz", "0X78797A"}, + {"%# x", "xyz", "0x78 0x79 0x7a"}, + {"%# X", "xyz", "0X78 0X79 0X7A"}, + + // basic bytes + {"%s", []byte("abc"), "abc"}, + {"%s", [3]byte{'a', 'b', 'c'}, "abc"}, + {"%s", &[3]byte{'a', 'b', 'c'}, "&abc"}, + {"%q", []byte("abc"), `"abc"`}, + {"%x", []byte("abc"), "616263"}, + {"%x", []byte("\xff\xf0\x0f\xff"), "fff00fff"}, + {"%X", []byte("\xff\xf0\x0f\xff"), "FFF00FFF"}, + {"%x", []byte(""), ""}, + {"% x", []byte(""), ""}, + {"%#x", []byte(""), ""}, + {"%# x", []byte(""), ""}, + {"%x", []byte("xyz"), "78797a"}, + {"%X", []byte("xyz"), "78797A"}, + {"% x", []byte("xyz"), "78 79 7a"}, + {"% X", []byte("xyz"), "78 79 7A"}, + {"%#x", []byte("xyz"), "0x78797a"}, + {"%#X", []byte("xyz"), "0X78797A"}, + {"%# x", []byte("xyz"), "0x78 0x79 0x7a"}, + {"%# X", []byte("xyz"), "0X78 0X79 0X7A"}, + + // escaped strings + {"%q", "", `""`}, + {"%#q", "", "``"}, + {"%q", "\"", `"\""`}, + {"%#q", "\"", "`\"`"}, + {"%q", "`", `"` + "`" + `"`}, + {"%#q", "`", `"` + "`" + `"`}, + {"%q", "\n", `"\n"`}, + {"%#q", "\n", `"\n"`}, + {"%q", `\n`, `"\\n"`}, + {"%#q", `\n`, "`\\n`"}, + {"%q", "abc", `"abc"`}, + {"%#q", "abc", "`abc`"}, + {"%q", "日本語", `"日本語"`}, + {"%+q", "日本語", `"\u65e5\u672c\u8a9e"`}, + {"%#q", "日本語", "`日本語`"}, + {"%#+q", "日本語", "`日本語`"}, + {"%q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`}, + {"%+q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`}, + {"%#q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`}, + {"%#+q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`}, + {"%q", "☺", `"☺"`}, + {"% q", "☺", `"☺"`}, // The space modifier should have no effect. + {"%+q", "☺", `"\u263a"`}, + {"%#q", "☺", "`☺`"}, + {"%#+q", "☺", "`☺`"}, + {"%10q", "⌘", ` "⌘"`}, + {"%+10q", "⌘", ` "\u2318"`}, + {"%-10q", "⌘", `"⌘" `}, + {"%+-10q", "⌘", `"\u2318" `}, + {"%010q", "⌘", `0000000"⌘"`}, + {"%+010q", "⌘", `00"\u2318"`}, + {"%-010q", "⌘", `"⌘" `}, // 0 has no effect when - is present. + {"%+-010q", "⌘", `"\u2318" `}, + {"%#8q", "\n", ` "\n"`}, + {"%#+8q", "\r", ` "\r"`}, + {"%#-8q", "\t", "` ` "}, + {"%#+-8q", "\b", `"\b" `}, + {"%q", "abc\xffdef", `"abc\xffdef"`}, + {"%+q", "abc\xffdef", `"abc\xffdef"`}, + {"%#q", "abc\xffdef", `"abc\xffdef"`}, + {"%#+q", "abc\xffdef", `"abc\xffdef"`}, + // Runes that are not printable. + {"%q", "\U0010ffff", `"\U0010ffff"`}, + {"%+q", "\U0010ffff", `"\U0010ffff"`}, + {"%#q", "\U0010ffff", "`􏿿`"}, + {"%#+q", "\U0010ffff", "`􏿿`"}, + // Runes that are not valid. + {"%q", string(rune(0x110000)), `"�"`}, + {"%+q", string(rune(0x110000)), `"\ufffd"`}, + {"%#q", string(rune(0x110000)), "`�`"}, + {"%#+q", string(rune(0x110000)), "`�`"}, + + // characters + {"%c", uint('x'), "x"}, + {"%c", 0xe4, "ä"}, + {"%c", 0x672c, "本"}, + {"%c", '日', "日"}, + {"%.0c", '⌘', "⌘"}, // Specifying precision should have no effect. + {"%3c", '⌘', " ⌘"}, + {"%-3c", '⌘', "⌘ "}, + {"%c", uint64(0x100000000), "\ufffd"}, + // Runes that are not printable. + {"%c", '\U00000e00', "\u0e00"}, + {"%c", '\U0010ffff', "\U0010ffff"}, + // Runes that are not valid. + {"%c", -1, "�"}, + {"%c", 0xDC80, "�"}, + {"%c", rune(0x110000), "�"}, + {"%c", int64(0xFFFFFFFFF), "�"}, + {"%c", uint64(0xFFFFFFFFF), "�"}, + + // escaped characters + {"%q", uint(0), `'\x00'`}, + {"%+q", uint(0), `'\x00'`}, + {"%q", '"', `'"'`}, + {"%+q", '"', `'"'`}, + {"%q", '\'', `'\''`}, + {"%+q", '\'', `'\''`}, + {"%q", '`', "'`'"}, + {"%+q", '`', "'`'"}, + {"%q", 'x', `'x'`}, + {"%+q", 'x', `'x'`}, + {"%q", 'ÿ', `'ÿ'`}, + {"%+q", 'ÿ', `'\u00ff'`}, + {"%q", '\n', `'\n'`}, + {"%+q", '\n', `'\n'`}, + {"%q", '☺', `'☺'`}, + {"%+q", '☺', `'\u263a'`}, + {"% q", '☺', `'☺'`}, // The space modifier should have no effect. + {"%.0q", '☺', `'☺'`}, // Specifying precision should have no effect. + {"%10q", '⌘', ` '⌘'`}, + {"%+10q", '⌘', ` '\u2318'`}, + {"%-10q", '⌘', `'⌘' `}, + {"%+-10q", '⌘', `'\u2318' `}, + {"%010q", '⌘', `0000000'⌘'`}, + {"%+010q", '⌘', `00'\u2318'`}, + {"%-010q", '⌘', `'⌘' `}, // 0 has no effect when - is present. + {"%+-010q", '⌘', `'\u2318' `}, + // Runes that are not printable. + {"%q", '\U00000e00', `'\u0e00'`}, + {"%q", '\U0010ffff', `'\U0010ffff'`}, + // Runes that are not valid. + {"%q", int32(-1), `'�'`}, + {"%q", 0xDC80, `'�'`}, + {"%q", rune(0x110000), `'�'`}, + {"%q", int64(0xFFFFFFFFF), `'�'`}, + {"%q", uint64(0xFFFFFFFFF), `'�'`}, + + // width + {"%5s", "abc", " abc"}, + {"%5s", []byte("abc"), " abc"}, + {"%2s", "\u263a", " ☺"}, + {"%2s", []byte("\u263a"), " ☺"}, + {"%-5s", "abc", "abc "}, + {"%-5s", []byte("abc"), "abc "}, + {"%05s", "abc", "00abc"}, + {"%05s", []byte("abc"), "00abc"}, + {"%5s", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz"}, + {"%5s", []byte("abcdefghijklmnopqrstuvwxyz"), "abcdefghijklmnopqrstuvwxyz"}, + {"%.5s", "abcdefghijklmnopqrstuvwxyz", "abcde"}, + {"%.5s", []byte("abcdefghijklmnopqrstuvwxyz"), "abcde"}, + {"%.0s", "日本語日本語", ""}, + {"%.0s", []byte("日本語日本語"), ""}, + {"%.5s", "日本語日本語", "日本語日本"}, + {"%.5s", []byte("日本語日本語"), "日本語日本"}, + {"%.10s", "日本語日本語", "日本語日本語"}, + {"%.10s", []byte("日本語日本語"), "日本語日本語"}, + {"%08q", "abc", `000"abc"`}, + {"%08q", []byte("abc"), `000"abc"`}, + {"%-8q", "abc", `"abc" `}, + {"%-8q", []byte("abc"), `"abc" `}, + {"%.5q", "abcdefghijklmnopqrstuvwxyz", `"abcde"`}, + {"%.5q", []byte("abcdefghijklmnopqrstuvwxyz"), `"abcde"`}, + {"%.5x", "abcdefghijklmnopqrstuvwxyz", "6162636465"}, + {"%.5x", []byte("abcdefghijklmnopqrstuvwxyz"), "6162636465"}, + {"%.3q", "日本語日本語", `"日本語"`}, + {"%.3q", []byte("日本語日本語"), `"日本語"`}, + {"%.1q", "日本語", `"日"`}, + {"%.1q", []byte("日本語"), `"日"`}, + {"%.1x", "日本語", "e6"}, + {"%.1X", []byte("日本語"), "E6"}, + {"%10.1q", "日本語日本語", ` "日"`}, + {"%10.1q", []byte("日本語日本語"), ` "日"`}, + {"%10v", nil, " "}, + {"%-10v", nil, " "}, + + // integers + {"%d", uint(12345), "12345"}, + {"%d", int(-12345), "-12345"}, + {"%d", ^uint8(0), "255"}, + {"%d", ^uint16(0), "65535"}, + {"%d", ^uint32(0), "4294967295"}, + {"%d", ^uint64(0), "18446744073709551615"}, + {"%d", int8(-1 << 7), "-128"}, + {"%d", int16(-1 << 15), "-32768"}, + {"%d", int32(-1 << 31), "-2147483648"}, + {"%d", int64(-1 << 63), "-9223372036854775808"}, + {"%.d", 0, ""}, + {"%.0d", 0, ""}, + {"%6.0d", 0, " "}, + {"%06.0d", 0, " "}, + {"% d", 12345, " 12345"}, + {"%+d", 12345, "+12345"}, + {"%+d", -12345, "-12345"}, + {"%b", 7, "111"}, + {"%b", -6, "-110"}, + {"%#b", 7, "0b111"}, + {"%#b", -6, "-0b110"}, + {"%b", ^uint32(0), "11111111111111111111111111111111"}, + {"%b", ^uint64(0), "1111111111111111111111111111111111111111111111111111111111111111"}, + {"%b", int64(-1 << 63), zeroFill("-1", 63, "")}, + {"%o", 01234, "1234"}, + {"%o", -01234, "-1234"}, + {"%#o", 01234, "01234"}, + {"%#o", -01234, "-01234"}, + {"%O", 01234, "0o1234"}, + {"%O", -01234, "-0o1234"}, + {"%o", ^uint32(0), "37777777777"}, + {"%o", ^uint64(0), "1777777777777777777777"}, + {"%#X", 0, "0X0"}, + {"%x", 0x12abcdef, "12abcdef"}, + {"%X", 0x12abcdef, "12ABCDEF"}, + {"%x", ^uint32(0), "ffffffff"}, + {"%X", ^uint64(0), "FFFFFFFFFFFFFFFF"}, + {"%.20b", 7, "00000000000000000111"}, + {"%10d", 12345, " 12345"}, + {"%10d", -12345, " -12345"}, + {"%+10d", 12345, " +12345"}, + {"%010d", 12345, "0000012345"}, + {"%010d", -12345, "-000012345"}, + {"%20.8d", 1234, " 00001234"}, + {"%20.8d", -1234, " -00001234"}, + {"%020.8d", 1234, " 00001234"}, + {"%020.8d", -1234, " -00001234"}, + {"%-20.8d", 1234, "00001234 "}, + {"%-20.8d", -1234, "-00001234 "}, + {"%-#20.8x", 0x1234abc, "0x01234abc "}, + {"%-#20.8X", 0x1234abc, "0X01234ABC "}, + {"%-#20.8o", 01234, "00001234 "}, + + // Test correct f.intbuf overflow checks. + {"%068d", 1, zeroFill("", 68, "1")}, + {"%068d", -1, zeroFill("-", 67, "1")}, + {"%#.68x", 42, zeroFill("0x", 68, "2a")}, + {"%.68d", -42, zeroFill("-", 68, "42")}, + {"%+.68d", 42, zeroFill("+", 68, "42")}, + {"% .68d", 42, zeroFill(" ", 68, "42")}, + {"% +.68d", 42, zeroFill("+", 68, "42")}, + + // unicode format + {"%U", 0, "U+0000"}, + {"%U", -1, "U+FFFFFFFFFFFFFFFF"}, + {"%U", '\n', `U+000A`}, + {"%#U", '\n', `U+000A`}, + {"%+U", 'x', `U+0078`}, // Plus flag should have no effect. + {"%# U", 'x', `U+0078 'x'`}, // Space flag should have no effect. + {"%#.2U", 'x', `U+0078 'x'`}, // Precisions below 4 should print 4 digits. + {"%U", '\u263a', `U+263A`}, + {"%#U", '\u263a', `U+263A '☺'`}, + {"%U", '\U0001D6C2', `U+1D6C2`}, + {"%#U", '\U0001D6C2', `U+1D6C2 '𝛂'`}, + {"%#14.6U", '⌘', " U+002318 '⌘'"}, + {"%#-14.6U", '⌘', "U+002318 '⌘' "}, + {"%#014.6U", '⌘', " U+002318 '⌘'"}, + {"%#-014.6U", '⌘', "U+002318 '⌘' "}, + {"%.68U", uint(42), zeroFill("U+", 68, "2A")}, + {"%#.68U", '日', zeroFill("U+", 68, "65E5") + " '日'"}, + + // floats + {"%+.3e", 0.0, "+0.000e+00"}, + {"%+.3e", 1.0, "+1.000e+00"}, + {"%+.3x", 0.0, "+0x0.000p+00"}, + {"%+.3x", 1.0, "+0x1.000p+00"}, + {"%+.3f", -1.0, "-1.000"}, + {"%+.3F", -1.0, "-1.000"}, + {"%+.3F", float32(-1.0), "-1.000"}, + {"%+07.2f", 1.0, "+001.00"}, + {"%+07.2f", -1.0, "-001.00"}, + {"%-07.2f", 1.0, "1.00 "}, + {"%-07.2f", -1.0, "-1.00 "}, + {"%+-07.2f", 1.0, "+1.00 "}, + {"%+-07.2f", -1.0, "-1.00 "}, + {"%-+07.2f", 1.0, "+1.00 "}, + {"%-+07.2f", -1.0, "-1.00 "}, + {"%+10.2f", +1.0, " +1.00"}, + {"%+10.2f", -1.0, " -1.00"}, + {"% .3E", -1.0, "-1.000E+00"}, + {"% .3e", 1.0, " 1.000e+00"}, + {"% .3X", -1.0, "-0X1.000P+00"}, + {"% .3x", 1.0, " 0x1.000p+00"}, + {"%+.3g", 0.0, "+0"}, + {"%+.3g", 1.0, "+1"}, + {"%+.3g", -1.0, "-1"}, + {"% .3g", -1.0, "-1"}, + {"% .3g", 1.0, " 1"}, + {"%b", float32(1.0), "8388608p-23"}, + {"%b", 1.0, "4503599627370496p-52"}, + // Test sharp flag used with floats. + {"%#g", 1e-323, "1.00000e-323"}, + {"%#g", -1.0, "-1.00000"}, + {"%#g", 1.1, "1.10000"}, + {"%#g", 123456.0, "123456."}, + {"%#g", 1234567.0, "1.234567e+06"}, + {"%#g", 1230000.0, "1.23000e+06"}, + {"%#g", 1000000.0, "1.00000e+06"}, + {"%#.0f", 1.0, "1."}, + {"%#.0e", 1.0, "1.e+00"}, + {"%#.0x", 1.0, "0x1.p+00"}, + {"%#.0g", 1.0, "1."}, + {"%#.0g", 1100000.0, "1.e+06"}, + {"%#.4f", 1.0, "1.0000"}, + {"%#.4e", 1.0, "1.0000e+00"}, + {"%#.4x", 1.0, "0x1.0000p+00"}, + {"%#.4g", 1.0, "1.000"}, + {"%#.4g", 100000.0, "1.000e+05"}, + {"%#.4g", 1.234, "1.234"}, + {"%#.4g", 0.1234, "0.1234"}, + {"%#.4g", 1.23, "1.230"}, + {"%#.4g", 0.123, "0.1230"}, + {"%#.4g", 1.2, "1.200"}, + {"%#.4g", 0.12, "0.1200"}, + {"%#.4g", 10.2, "10.20"}, + {"%#.4g", 0.0, "0.000"}, + {"%#.4g", 0.012, "0.01200"}, + {"%#.0f", 123.0, "123."}, + {"%#.0e", 123.0, "1.e+02"}, + {"%#.0x", 123.0, "0x1.p+07"}, + {"%#.0g", 123.0, "1.e+02"}, + {"%#.4f", 123.0, "123.0000"}, + {"%#.4e", 123.0, "1.2300e+02"}, + {"%#.4x", 123.0, "0x1.ec00p+06"}, + {"%#.4g", 123.0, "123.0"}, + {"%#.4g", 123000.0, "1.230e+05"}, + {"%#9.4g", 1.0, " 1.000"}, + // The sharp flag has no effect for binary float format. + {"%#b", 1.0, "4503599627370496p-52"}, + // Precision has no effect for binary float format. + {"%.4b", float32(1.0), "8388608p-23"}, + {"%.4b", -1.0, "-4503599627370496p-52"}, + // Test correct f.intbuf boundary checks. + {"%.68f", 1.0, zeroFill("1.", 68, "")}, + {"%.68f", -1.0, zeroFill("-1.", 68, "")}, + // float infinites and NaNs + {"%f", posInf, "+Inf"}, + {"%.1f", negInf, "-Inf"}, + {"% f", NaN, " NaN"}, + {"%20f", posInf, " +Inf"}, + {"% 20F", posInf, " Inf"}, + {"% 20e", negInf, " -Inf"}, + {"% 20x", negInf, " -Inf"}, + {"%+20E", negInf, " -Inf"}, + {"%+20X", negInf, " -Inf"}, + {"% +20g", negInf, " -Inf"}, + {"%+-20G", posInf, "+Inf "}, + {"%20e", NaN, " NaN"}, + {"%20x", NaN, " NaN"}, + {"% +20E", NaN, " +NaN"}, + {"% +20X", NaN, " +NaN"}, + {"% -20g", NaN, " NaN "}, + {"%+-20G", NaN, "+NaN "}, + // Zero padding does not apply to infinities and NaN. + {"%+020e", posInf, " +Inf"}, + {"%+020x", posInf, " +Inf"}, + {"%-020f", negInf, "-Inf "}, + {"%-020E", NaN, "NaN "}, + {"%-020X", NaN, "NaN "}, + + // old test/fmt_test.go + {"%e", 1.0, "1.000000e+00"}, + {"%e", 1234.5678e3, "1.234568e+06"}, + {"%e", 1234.5678e-8, "1.234568e-05"}, + {"%e", -7.0, "-7.000000e+00"}, + {"%e", -1e-9, "-1.000000e-09"}, + {"%f", 1234.5678e3, "1234567.800000"}, + {"%f", 1234.5678e-8, "0.000012"}, + {"%f", -7.0, "-7.000000"}, + {"%f", -1e-9, "-0.000000"}, + {"%g", 1234.5678e3, "1.2345678e+06"}, + {"%g", float32(1234.5678e3), "1.2345678e+06"}, + {"%g", 1234.5678e-8, "1.2345678e-05"}, + {"%g", -7.0, "-7"}, + {"%g", -1e-9, "-1e-09"}, + {"%g", float32(-1e-9), "-1e-09"}, + {"%E", 1.0, "1.000000E+00"}, + {"%E", 1234.5678e3, "1.234568E+06"}, + {"%E", 1234.5678e-8, "1.234568E-05"}, + {"%E", -7.0, "-7.000000E+00"}, + {"%E", -1e-9, "-1.000000E-09"}, + {"%G", 1234.5678e3, "1.2345678E+06"}, + {"%G", float32(1234.5678e3), "1.2345678E+06"}, + {"%G", 1234.5678e-8, "1.2345678E-05"}, + {"%G", -7.0, "-7"}, + {"%G", -1e-9, "-1E-09"}, + {"%G", float32(-1e-9), "-1E-09"}, + {"%20.5s", "qwertyuiop", " qwert"}, + {"%.5s", "qwertyuiop", "qwert"}, + {"%-20.5s", "qwertyuiop", "qwert "}, + {"%20c", 'x', " x"}, + {"%-20c", 'x', "x "}, + {"%20.6e", 1.2345e3, " 1.234500e+03"}, + {"%20.6e", 1.2345e-3, " 1.234500e-03"}, + {"%20e", 1.2345e3, " 1.234500e+03"}, + {"%20e", 1.2345e-3, " 1.234500e-03"}, + {"%20.8e", 1.2345e3, " 1.23450000e+03"}, + {"%20f", 1.23456789e3, " 1234.567890"}, + {"%20f", 1.23456789e-3, " 0.001235"}, + {"%20f", 12345678901.23456789, " 12345678901.234568"}, + {"%-20f", 1.23456789e3, "1234.567890 "}, + {"%20.8f", 1.23456789e3, " 1234.56789000"}, + {"%20.8f", 1.23456789e-3, " 0.00123457"}, + {"%g", 1.23456789e3, "1234.56789"}, + {"%g", 1.23456789e-3, "0.00123456789"}, + {"%g", 1.23456789e20, "1.23456789e+20"}, + + // arrays + {"%v", array, "[1 2 3 4 5]"}, + {"%v", iarray, "[1 hello 2.5 ]"}, + {"%v", barray, "[1 2 3 4 5]"}, + {"%v", &array, "&[1 2 3 4 5]"}, + {"%v", &iarray, "&[1 hello 2.5 ]"}, + {"%v", &barray, "&[1 2 3 4 5]"}, + + // slices + {"%v", slice, "[1 2 3 4 5]"}, + {"%v", islice, "[1 hello 2.5 ]"}, + {"%v", bslice, "[1 2 3 4 5]"}, + {"%v", &slice, "&[1 2 3 4 5]"}, + {"%v", &islice, "&[1 hello 2.5 ]"}, + {"%v", &bslice, "&[1 2 3 4 5]"}, + + // byte arrays and slices with %b,%c,%d,%o,%U and %v + {"%b", [3]byte{65, 66, 67}, "[1000001 1000010 1000011]"}, + {"%c", [3]byte{65, 66, 67}, "[A B C]"}, + {"%d", [3]byte{65, 66, 67}, "[65 66 67]"}, + {"%o", [3]byte{65, 66, 67}, "[101 102 103]"}, + {"%U", [3]byte{65, 66, 67}, "[U+0041 U+0042 U+0043]"}, + {"%v", [3]byte{65, 66, 67}, "[65 66 67]"}, + {"%v", [1]byte{123}, "[123]"}, + {"%012v", []byte{}, "[]"}, + {"%#012v", []byte{}, "[]byte{}"}, + {"%6v", []byte{1, 11, 111}, "[ 1 11 111]"}, + {"%06v", []byte{1, 11, 111}, "[000001 000011 000111]"}, + {"%-6v", []byte{1, 11, 111}, "[1 11 111 ]"}, + {"%-06v", []byte{1, 11, 111}, "[1 11 111 ]"}, + {"%#v", []byte{1, 11, 111}, "[]byte{0x1, 0xb, 0x6f}"}, + {"%#6v", []byte{1, 11, 111}, "[]byte{ 0x1, 0xb, 0x6f}"}, + {"%#06v", []byte{1, 11, 111}, "[]byte{0x000001, 0x00000b, 0x00006f}"}, + {"%#-6v", []byte{1, 11, 111}, "[]byte{0x1 , 0xb , 0x6f }"}, + {"%#-06v", []byte{1, 11, 111}, "[]byte{0x1 , 0xb , 0x6f }"}, + // f.space should and f.plus should not have an effect with %v. + {"% v", []byte{1, 11, 111}, "[ 1 11 111]"}, + {"%+v", [3]byte{1, 11, 111}, "[1 11 111]"}, + {"%# -6v", []byte{1, 11, 111}, "[]byte{ 0x1 , 0xb , 0x6f }"}, + {"%#+-6v", [3]byte{1, 11, 111}, "[3]uint8{0x1 , 0xb , 0x6f }"}, + // f.space and f.plus should have an effect with %d. + {"% d", []byte{1, 11, 111}, "[ 1 11 111]"}, + {"%+d", [3]byte{1, 11, 111}, "[+1 +11 +111]"}, + {"%# -6d", []byte{1, 11, 111}, "[ 1 11 111 ]"}, + {"%#+-6d", [3]byte{1, 11, 111}, "[+1 +11 +111 ]"}, + + // floates with %v + {"%v", 1.2345678, "1.2345678"}, + {"%v", float32(1.2345678), "1.2345678"}, + + // structs + {"%v", A{1, 2, "a", []int{1, 2}}, `{1 2 a [1 2]}`}, + {"%+v", A{1, 2, "a", []int{1, 2}}, `{i:1 j:2 s:a x:[1 2]}`}, + + // +v on structs with Stringable items + {"%+v", B{1, 2}, `{I:<1> j:2}`}, + {"%+v", C{1, B{2, 3}}, `{i:1 B:{I:<2> j:3}}`}, + + // other formats on Stringable items + {"%s", I(23), `<23>`}, + {"%q", I(23), `"<23>"`}, + {"%x", I(23), `3c32333e`}, + {"%#x", I(23), `0x3c32333e`}, + {"%# x", I(23), `0x3c 0x32 0x33 0x3e`}, + // Stringer applies only to string formats. + {"%d", I(23), `23`}, + + // go syntax + {"%#v", A{1, 2, "a", []int{1, 2}}, `fmt_test.A{i:1, j:0x2, s:"a", x:[]int{1, 2}}`}, + {"%#v", new(byte), "(*uint8)(0xPTR)"}, + {"%#v", TestFmtInterface, "(func(*testing.T))(0xPTR)"}, + {"%#v", uint64(1<<64 - 1), "0xffffffffffffffff"}, + {"%#v", 1000000000, "1000000000"}, + {"%#v", map[string]int{"a": 1}, `map[string]int{"a":1}`}, + {"%#v", map[string]B{"a": {1, 2}}, `map[string]fmt_test.B{"a":fmt_test.B{I:1, j:2}}`}, + {"%#v", []string{"a", "b"}, `[]string{"a", "b"}`}, + {"%#v", SI{}, `fmt_test.SI{I:}`}, + {"%#v", []int(nil), `[]int(nil)`}, + {"%#v", []int{}, `[]int{}`}, + {"%#v", array, `[5]int{1, 2, 3, 4, 5}`}, + {"%#v", &array, `&[5]int{1, 2, 3, 4, 5}`}, + // XXX: in Gno, our version of reflect isn't able to properly handle interface + // types, so even in %#v they just show up as "nil". + {"%#v", iarray, `[4]interface {}{1, "hello", 2.5, }`}, + {"%#v", &iarray, `&[4]interface {}{1, "hello", 2.5, }`}, + {"%#v", map[int]byte(nil), `map[int]uint8(nil)`}, + {"%#v", map[int]byte{}, `map[int]uint8{}`}, + {"%#v", "foo", `"foo"`}, + {"%#v", barray, `[5]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`}, + {"%#v", bslice, `[]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`}, + {"%#v", []int32(nil), "[]int32(nil)"}, + {"%#v", 1.2345678, "1.2345678"}, + {"%#v", float32(1.2345678), "1.2345678"}, + + // Whole number floats are printed without decimals. See Issue 27634. + {"%#v", 1.0, "1"}, + {"%#v", 1000000.0, "1e+06"}, + {"%#v", float32(1.0), "1"}, + {"%#v", float32(1000000.0), "1e+06"}, + + // Only print []byte and []uint8 as type []byte if they appear at the top level. + {"%#v", []byte(nil), "[]byte(nil)"}, + {"%#v", []uint8(nil), "[]byte(nil)"}, + {"%#v", []byte{}, "[]byte{}"}, + {"%#v", []uint8{}, "[]byte{}"}, + {"%#v", &[]byte{}, "&[]uint8{}"}, + {"%#v", &[]byte{}, "&[]uint8{}"}, + {"%#v", [3]byte{}, "[3]uint8{0x0, 0x0, 0x0}"}, + {"%#v", [3]uint8{}, "[3]uint8{0x0, 0x0, 0x0}"}, + + // slices with other formats + {"%#x", []int{1, 2, 15}, `[0x1 0x2 0xf]`}, + {"%x", []int{1, 2, 15}, `[1 2 f]`}, + {"%d", []int{1, 2, 15}, `[1 2 15]`}, + {"%d", []byte{1, 2, 15}, `[1 2 15]`}, + {"%q", []string{"a", "b"}, `["a" "b"]`}, + {"% 02x", []byte{1}, "01"}, + {"% 02x", []byte{1, 2, 3}, "01 02 03"}, + + // Padding with byte slices. + {"%2x", []byte{}, " "}, + {"%#2x", []byte{}, " "}, + {"% 02x", []byte{}, "00"}, + {"%# 02x", []byte{}, "00"}, + {"%-2x", []byte{}, " "}, + {"%-02x", []byte{}, " "}, + {"%8x", []byte{0xab}, " ab"}, + {"% 8x", []byte{0xab}, " ab"}, + {"%#8x", []byte{0xab}, " 0xab"}, + {"%# 8x", []byte{0xab}, " 0xab"}, + {"%08x", []byte{0xab}, "000000ab"}, + {"% 08x", []byte{0xab}, "000000ab"}, + {"%#08x", []byte{0xab}, "00000xab"}, + {"%# 08x", []byte{0xab}, "00000xab"}, + {"%10x", []byte{0xab, 0xcd}, " abcd"}, + {"% 10x", []byte{0xab, 0xcd}, " ab cd"}, + {"%#10x", []byte{0xab, 0xcd}, " 0xabcd"}, + {"%# 10x", []byte{0xab, 0xcd}, " 0xab 0xcd"}, + {"%010x", []byte{0xab, 0xcd}, "000000abcd"}, + {"% 010x", []byte{0xab, 0xcd}, "00000ab cd"}, + {"%#010x", []byte{0xab, 0xcd}, "00000xabcd"}, + {"%# 010x", []byte{0xab, 0xcd}, "00xab 0xcd"}, + {"%-10X", []byte{0xab}, "AB "}, + {"% -010X", []byte{0xab}, "AB "}, + {"%#-10X", []byte{0xab, 0xcd}, "0XABCD "}, + {"%# -010X", []byte{0xab, 0xcd}, "0XAB 0XCD "}, + // Same for strings + {"%2x", "", " "}, + {"%#2x", "", " "}, + {"% 02x", "", "00"}, + {"%# 02x", "", "00"}, + {"%-2x", "", " "}, + {"%-02x", "", " "}, + {"%8x", "\xab", " ab"}, + {"% 8x", "\xab", " ab"}, + {"%#8x", "\xab", " 0xab"}, + {"%# 8x", "\xab", " 0xab"}, + {"%08x", "\xab", "000000ab"}, + {"% 08x", "\xab", "000000ab"}, + {"%#08x", "\xab", "00000xab"}, + {"%# 08x", "\xab", "00000xab"}, + {"%10x", "\xab\xcd", " abcd"}, + {"% 10x", "\xab\xcd", " ab cd"}, + {"%#10x", "\xab\xcd", " 0xabcd"}, + {"%# 10x", "\xab\xcd", " 0xab 0xcd"}, + {"%010x", "\xab\xcd", "000000abcd"}, + {"% 010x", "\xab\xcd", "00000ab cd"}, + {"%#010x", "\xab\xcd", "00000xabcd"}, + {"%# 010x", "\xab\xcd", "00xab 0xcd"}, + {"%-10X", "\xab", "AB "}, + {"% -010X", "\xab", "AB "}, + {"%#-10X", "\xab\xcd", "0XABCD "}, + {"%# -010X", "\xab\xcd", "0XAB 0XCD "}, + + // renamings + {"%v", renamedBool(true), "true"}, + {"%d", renamedBool(true), "%!d(fmt_test.renamedBool=true)"}, + {"%o", renamedInt(8), "10"}, + {"%d", renamedInt8(-9), "-9"}, + {"%v", renamedInt16(10), "10"}, + {"%v", renamedInt32(-11), "-11"}, + {"%X", renamedInt64(255), "FF"}, + {"%v", renamedUint(13), "13"}, + {"%o", renamedUint8(14), "16"}, + {"%X", renamedUint16(15), "F"}, + {"%d", renamedUint32(16), "16"}, + {"%X", renamedUint64(17), "11"}, + {"%x", renamedString("thing"), "7468696e67"}, + {"%d", renamedBytes([]byte{1, 2, 15}), `[1 2 15]`}, + {"%q", renamedBytes([]byte("hello")), `"hello"`}, + {"%x", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, "68656c6c6f"}, + {"%X", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, "68656C6C6F"}, + {"%s", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, "hello"}, + {"%q", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, `"hello"`}, + {"%v", renamedFloat32(22), "22"}, + {"%v", renamedFloat64(33), "33"}, + + // Formatter + {"%x", F(1), ""}, + {"%x", G(2), "2"}, + {"%+v", S{F(4), G(5)}, "{F: G:5}"}, + + // GoStringer + {"%#v", G(6), "GoString(6)"}, + {"%#v", S{F(7), G(8)}, "fmt_test.S{F:, G:GoString(8)}"}, + + // %T + {"%T", byte(0), "uint8"}, + {"%T", intVar, "int"}, + {"%6T", &intVar, " *int"}, + {"%10T", nil, " "}, + {"%-10T", nil, " "}, + + // %p with pointers + {"%p", (*int)(nil), "0x0"}, + {"%#p", (*int)(nil), "0"}, + {"%p", &intVar, "0xPTR"}, + {"%#p", &intVar, "PTR"}, + {"%p", &array, "0xPTR"}, + {"%p", &slice, "0xPTR"}, + {"%8.2p", (*int)(nil), " 0x00"}, + {"%-20.16p", &intVar, "0xPTR "}, + // %p on non-pointers + {"%p", make(map[int]int), "0xPTR"}, + {"%p", func() {}, "0xPTR"}, + {"%p", 27, "%!p(int=27)"}, // not a pointer at all + {"%p", nil, "%!p()"}, // nil on its own has no type ... + {"%#p", nil, "%!p()"}, // ... and hence is not a pointer type. + // pointers with specified base + {"%b", &intVar, "PTR_b"}, + {"%d", &intVar, "PTR_d"}, + {"%o", &intVar, "PTR_o"}, + {"%x", &intVar, "PTR_x"}, + {"%X", &intVar, "PTR_X"}, + // %v on pointers + {"%v", nil, ""}, + {"%#v", nil, ""}, + {"%v", (*int)(nil), ""}, + {"%#v", (*int)(nil), "(*int)(nil)"}, + {"%v", &intVar, "0xPTR"}, + {"%#v", &intVar, "(*int)(0xPTR)"}, + {"%8.2v", (*int)(nil), " "}, + {"%-20.16v", &intVar, "0xPTR "}, + // string method on pointer + {"%s", &pValue, "String(p)"}, // String method... + {"%p", &pValue, "0xPTR"}, // ... is not called with %p. + + // %d on Stringer should give integer if possible + {"%s", time.Time{}.Month(), "January"}, + {"%d", time.Time{}.Month(), "1"}, + + // erroneous things + {"", nil, "%!(EXTRA )"}, + {"", 2, "%!(EXTRA int=2)"}, + {"no args", "hello", "no args%!(EXTRA string=hello)"}, + {"%s %", "hello", "hello %!(NOVERB)"}, + {"%s %.2", "hello", "hello %!(NOVERB)"}, + {"%017091901790959340919092959340919017929593813360", 0, "%!(NOVERB)%!(EXTRA int=0)"}, + {"%184467440737095516170v", 0, "%!(NOVERB)%!(EXTRA int=0)"}, + // Extra argument errors should format without flags set. + {"%010.2", "12345", "%!(NOVERB)%!(EXTRA string=12345)"}, + + // Test that maps with non-reflexive keys print all keys and values. + // XXX: Unsupported in gno: https://github.com/gnolang/gno/issues/3867 + // {"%v", map[float64]int{NaN: 1, NaN: 1}, "map[NaN:1 NaN:1]"}, + + // Comparison of padding rules with C printf. + /* + C program: + #include + + char *format[] = { + "[%.2f]", + "[% .2f]", + "[%+.2f]", + "[%7.2f]", + "[% 7.2f]", + "[%+7.2f]", + "[% +7.2f]", + "[%07.2f]", + "[% 07.2f]", + "[%+07.2f]", + "[% +07.2f]" + }; + + int main(void) { + int i; + for(i = 0; i < 11; i++) { + printf("%s: ", format[i]); + printf(format[i], 1.0); + printf(" "); + printf(format[i], -1.0); + printf("\n"); + } + } + + Output: + [%.2f]: [1.00] [-1.00] + [% .2f]: [ 1.00] [-1.00] + [%+.2f]: [+1.00] [-1.00] + [%7.2f]: [ 1.00] [ -1.00] + [% 7.2f]: [ 1.00] [ -1.00] + [%+7.2f]: [ +1.00] [ -1.00] + [% +7.2f]: [ +1.00] [ -1.00] + [%07.2f]: [0001.00] [-001.00] + [% 07.2f]: [ 001.00] [-001.00] + [%+07.2f]: [+001.00] [-001.00] + [% +07.2f]: [+001.00] [-001.00] + + */ + {"%.2f", 1.0, "1.00"}, + {"%.2f", -1.0, "-1.00"}, + {"% .2f", 1.0, " 1.00"}, + {"% .2f", -1.0, "-1.00"}, + {"%+.2f", 1.0, "+1.00"}, + {"%+.2f", -1.0, "-1.00"}, + {"%7.2f", 1.0, " 1.00"}, + {"%7.2f", -1.0, " -1.00"}, + {"% 7.2f", 1.0, " 1.00"}, + {"% 7.2f", -1.0, " -1.00"}, + {"%+7.2f", 1.0, " +1.00"}, + {"%+7.2f", -1.0, " -1.00"}, + {"% +7.2f", 1.0, " +1.00"}, + {"% +7.2f", -1.0, " -1.00"}, + {"%07.2f", 1.0, "0001.00"}, + {"%07.2f", -1.0, "-001.00"}, + {"% 07.2f", 1.0, " 001.00"}, + {"% 07.2f", -1.0, "-001.00"}, + {"%+07.2f", 1.0, "+001.00"}, + {"%+07.2f", -1.0, "-001.00"}, + {"% +07.2f", 1.0, "+001.00"}, + {"% +07.2f", -1.0, "-001.00"}, + + // Use spaces instead of zero if padding to the right. + {"%0-5s", "abc", "abc "}, + {"%-05.1f", 1.0, "1.0 "}, + + // float and complex formatting should not change the padding width + // for other elements. See issue 14642. + {"%06v", []any{+10.0, 10}, "[000010 000010]"}, + {"%06v", []any{-10.0, 10}, "[-00010 000010]"}, + + // integer formatting should not alter padding for other elements. + {"%03.6v", []any{1, 2.0, "x"}, "[000001 002 00x]"}, + {"%03.0v", []any{0, 2.0, "x"}, "[ 002 000]"}, + + // Incomplete format specification caused crash. + {"%.", 3, "%!.(int=3)"}, + + // []T where type T is a byte with a Stringer method. + {"%v", byteStringerSlice, "[X X X X X]"}, + {"%s", byteStringerSlice, "hello"}, + {"%q", byteStringerSlice, "\"hello\""}, + {"%x", byteStringerSlice, "68656c6c6f"}, + {"%X", byteStringerSlice, "68656C6C6F"}, + {"%#v", byteStringerSlice, "[]fmt_test.byteStringer{0x68, 0x65, 0x6c, 0x6c, 0x6f}"}, + + // And the same for Formatter. + {"%v", byteFormatterSlice, "[X X X X X]"}, + {"%s", byteFormatterSlice, "hello"}, + {"%q", byteFormatterSlice, "\"hello\""}, + {"%x", byteFormatterSlice, "68656c6c6f"}, + {"%X", byteFormatterSlice, "68656C6C6F"}, + // This next case seems wrong, but the docs say the Formatter wins here. + {"%#v", byteFormatterSlice, "[]fmt_test.byteFormatter{X, X, X, X, X}"}, + + // pp.WriteString + {"%s", writeStringFormatter(""), "******"}, + {"%s", writeStringFormatter("xyz"), "***xyz***"}, + {"%s", writeStringFormatter("⌘/⌘"), "***⌘/⌘***"}, + + // Tests to check that not supported verbs generate an error string. + {"%☠", nil, "%!☠()"}, + {"%☠", any(nil), "%!☠()"}, + {"%☠", int(0), "%!☠(int=0)"}, + {"%☠", uint(0), "%!☠(uint=0)"}, + {"%☠", []byte{0, 1}, "[%!☠(uint8=0) %!☠(uint8=1)]"}, + {"%☠", []uint8{0, 1}, "[%!☠(uint8=0) %!☠(uint8=1)]"}, + {"%☠", [1]byte{0}, "[%!☠(uint8=0)]"}, + {"%☠", [1]uint8{0}, "[%!☠(uint8=0)]"}, + {"%☠", "hello", "%!☠(string=hello)"}, + {"%☠", 1.2345678, "%!☠(float64=1.2345678)"}, + {"%☠", float32(1.2345678), "%!☠(float32=1.2345678)"}, + {"%☠", &intVar, "%!☠(*int=0xPTR)"}, + {"%☠", func() {}, "%!☠(func()=0xPTR)"}, + {"%☠", SI{renamedInt(0)}, "{%!☠(fmt_test.renamedInt=0)}"}, + {"%☠", &[]any{I(1), G(2)}, "&[%!☠(fmt_test.I=1) %!☠(fmt_test.G=2)]"}, + {"%☠", SI{&[]any{I(1), G(2)}}, "{%!☠(*[]interface {}=&[1 2])}"}, + {"%☠", map[float64]int{NaN: 1}, "map[%!☠(float64=NaN):%!☠(int=1)]"}, +} + +// zeroFill generates zero-filled strings of the specified width. The length +// of the suffix (but not the prefix) is compensated for in the width calculation. +func zeroFill(prefix string, width int, suffix string) string { + return prefix + strings.Repeat("0", width-len(suffix)) + suffix +} + +func TestSprintf(t *testing.T) { + for _, tt := range fmtTests { + s := fmt.Sprintf(tt.fmt, tt.val) + i := strings.Index(tt.out, "PTR") + if i >= 0 && i < len(s) { + var pattern, chars string + switch { + case strings.HasPrefix(tt.out[i:], "PTR_b"): + pattern = "PTR_b" + chars = "01" + case strings.HasPrefix(tt.out[i:], "PTR_o"): + pattern = "PTR_o" + chars = "01234567" + case strings.HasPrefix(tt.out[i:], "PTR_d"): + pattern = "PTR_d" + chars = "0123456789" + case strings.HasPrefix(tt.out[i:], "PTR_x"): + pattern = "PTR_x" + chars = "0123456789abcdef" + case strings.HasPrefix(tt.out[i:], "PTR_X"): + pattern = "PTR_X" + chars = "0123456789ABCDEF" + default: + pattern = "PTR" + chars = "0123456789abcdefABCDEF" + } + p := s[:i] + pattern + for j := i; j < len(s); j++ { + if !strings.ContainsRune(chars, rune(s[j])) { + p += s[j:] + break + } + } + s = p + } + if s != tt.out { + if _, ok := tt.val.(string); ok { + // Don't requote the already-quoted strings. + // It's too confusing to read the errors. + t.Errorf("Sprintf(%q, %q) = <%s> want <%s>", tt.fmt, tt.val, s, tt.out) + } else { + t.Errorf("Sprintf(%q, %v) = %q want %q", tt.fmt, tt.val, s, tt.out) + } + } + } +} + +type SE []any // slice of empty; notational compactness. + +var reorderTests = []struct { + fmt string + val SE + out string +}{ + {"%[1]d", SE{1}, "1"}, + {"%[2]d", SE{2, 1}, "1"}, + {"%[2]d %[1]d", SE{1, 2}, "2 1"}, + {"%[2]*[1]d", SE{2, 5}, " 2"}, + {"%6.2f", SE{12.0}, " 12.00"}, // Explicit version of next line. + {"%[3]*.[2]*[1]f", SE{12.0, 2, 6}, " 12.00"}, + {"%[1]*.[2]*[3]f", SE{6, 2, 12.0}, " 12.00"}, + {"%10f", SE{12.0}, " 12.000000"}, + {"%[1]*[3]f", SE{10, 99, 12.0}, " 12.000000"}, + {"%.6f", SE{12.0}, "12.000000"}, // Explicit version of next line. + {"%.[1]*[3]f", SE{6, 99, 12.0}, "12.000000"}, + {"%6.f", SE{12.0}, " 12"}, // // Explicit version of next line; empty precision means zero. + {"%[1]*.[3]f", SE{6, 3, 12.0}, " 12"}, + // An actual use! Print the same arguments twice. + {"%d %d %d %#[1]o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015"}, + + // Erroneous cases. + {"%[d", SE{2, 1}, "%!d(BADINDEX)"}, + {"%]d", SE{2, 1}, "%!](int=2)d%!(EXTRA int=1)"}, + {"%[]d", SE{2, 1}, "%!d(BADINDEX)"}, + {"%[-3]d", SE{2, 1}, "%!d(BADINDEX)"}, + {"%[99]d", SE{2, 1}, "%!d(BADINDEX)"}, + {"%[3]", SE{2, 1}, "%!(NOVERB)"}, + {"%[1].2d", SE{5, 6}, "%!d(BADINDEX)"}, + {"%[1]2d", SE{2, 1}, "%!d(BADINDEX)"}, + {"%3.[2]d", SE{7}, "%!d(BADINDEX)"}, + {"%.[2]d", SE{7}, "%!d(BADINDEX)"}, + {"%d %d %d %#[1]o %#o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015 %!o(MISSING)"}, + {"%[5]d %[2]d %d", SE{1, 2, 3}, "%!d(BADINDEX) 2 3"}, + {"%d %[3]d %d", SE{1, 2}, "1 %!d(BADINDEX) 2"}, // Erroneous index does not affect sequence. + {"%.[]", SE{}, "%!](BADINDEX)"}, // Issue 10675 + {"%.-3d", SE{42}, "%!-(int=42)3d"}, // TODO: Should this set return better error messages? + {"%2147483648d", SE{42}, "%!(NOVERB)%!(EXTRA int=42)"}, + {"%-2147483648d", SE{42}, "%!(NOVERB)%!(EXTRA int=42)"}, + {"%.2147483648d", SE{42}, "%!(NOVERB)%!(EXTRA int=42)"}, +} + +func TestReorder(t *testing.T) { + for _, tt := range reorderTests { + s := fmt.Sprintf(tt.fmt, tt.val...) + if s != tt.out { + t.Errorf("Sprintf(%q, %v) = <%s> want <%s>", tt.fmt, tt.val, s, tt.out) + } else { + } + } +} + +func BenchmarkSprintfPadding(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%16f", 1.0) + } + }) +} + +func BenchmarkSprintfEmpty(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("") + } + }) +} + +func BenchmarkSprintfString(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%s", "hello") + } + }) +} + +func BenchmarkSprintfTruncateString(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%.3s", "日本語日本語日本語日本語") + } + }) +} + +func BenchmarkSprintfTruncateBytes(b *testing.B) { + var bytes any = []byte("日本語日本語日本語日本語") + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%.3s", bytes) + } + }) +} + +func BenchmarkSprintfSlowParsingPath(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%.v", nil) + } + }) +} + +func BenchmarkSprintfQuoteString(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%q", "日本語日本語日本語") + } + }) +} + +func BenchmarkSprintfInt(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%d", 5) + } + }) +} + +func BenchmarkSprintfIntInt(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%d %d", 5, 6) + } + }) +} + +func BenchmarkSprintfPrefixedInt(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("This is some meaningless prefix text that needs to be scanned %d", 6) + } + }) +} + +func BenchmarkSprintfFloat(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%g", 5.23184) + } + }) +} + +func BenchmarkSprintfBoolean(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%t", true) + } + }) +} + +func BenchmarkSprintfHexString(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("% #x", "0123456789abcdef") + } + }) +} + +func BenchmarkSprintfHexBytes(b *testing.B) { + data := []byte("0123456789abcdef") + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("% #x", data) + } + }) +} + +func BenchmarkSprintfBytes(b *testing.B) { + data := []byte("0123456789abcdef") + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%v", data) + } + }) +} + +func BenchmarkSprintfStringer(b *testing.B) { + stringer := I(12345) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%v", stringer) + } + }) +} + +func BenchmarkSprintfStructure(b *testing.B) { + s := &[]any{SI{12345}, map[int]string{0: "hello"}} + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = fmt.Sprintf("%#v", s) + } + }) +} + +func BenchmarkManyArgs(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + var buf bytes.Buffer + for pb.Next() { + buf.Reset() + fmt.Fprintf(&buf, "%2d/%2d/%2d %d:%d:%d %s %s\n", 3, 4, 5, 11, 12, 13, "hello", "world") + } + }) +} + +func BenchmarkFprintInt(b *testing.B) { + var buf bytes.Buffer + for i := 0; i < b.N; i++ { + buf.Reset() + fmt.Fprint(&buf, 123456) + } +} + +func BenchmarkFprintfBytes(b *testing.B) { + data := []byte(string("0123456789")) + var buf bytes.Buffer + for i := 0; i < b.N; i++ { + buf.Reset() + fmt.Fprintf(&buf, "%s", data) + } +} + +func BenchmarkFprintIntNoAlloc(b *testing.B) { + var x any = 123456 + var buf bytes.Buffer + for i := 0; i < b.N; i++ { + buf.Reset() + fmt.Fprint(&buf, x) + } +} + +var ( + mallocBuf bytes.Buffer + mallocPointer *int // A pointer so we know the interface value won't allocate. +) + +var mallocTest = []struct { + count int + desc string + fn func() +}{ + {0, `Sprintf("")`, func() { _ = fmt.Sprintf("") }}, + {1, `Sprintf("xxx")`, func() { _ = fmt.Sprintf("xxx") }}, + {0, `Sprintf("%x")`, func() { _ = fmt.Sprintf("%x", 7) }}, + {1, `Sprintf("%x")`, func() { _ = fmt.Sprintf("%x", 1<<16) }}, + {3, `Sprintf("%80000s")`, func() { _ = fmt.Sprintf("%80000s", "hello") }}, // large buffer (>64KB) + {1, `Sprintf("%s")`, func() { _ = fmt.Sprintf("%s", "hello") }}, + {1, `Sprintf("%x %x")`, func() { _ = fmt.Sprintf("%x %x", 7, 112) }}, + {1, `Sprintf("%g")`, func() { _ = fmt.Sprintf("%g", float32(3.14159)) }}, + {0, `Fprintf(buf, "%s")`, func() { mallocBuf.Reset(); fmt.Fprintf(&mallocBuf, "%s", "hello") }}, + {0, `Fprintf(buf, "%x")`, func() { mallocBuf.Reset(); fmt.Fprintf(&mallocBuf, "%x", 7) }}, + {0, `Fprintf(buf, "%x")`, func() { mallocBuf.Reset(); fmt.Fprintf(&mallocBuf, "%x", 1<<16) }}, + {2, `Fprintf(buf, "%80000s")`, func() { mallocBuf.Reset(); fmt.Fprintf(&mallocBuf, "%80000s", "hello") }}, // large buffer (>64KB) + // If the interface value doesn't need to allocate, amortized allocation overhead should be zero. + {0, `Fprintf(buf, "%x %x %x")`, func() { + mallocBuf.Reset() + fmt.Fprintf(&mallocBuf, "%x %x %x", mallocPointer, mallocPointer, mallocPointer) + }}, +} + +var _ bytes.Buffer + +type flagPrinter struct{} + +func (flagPrinter) Format(f fmt.State, c rune) { + s := "%" + for i := 0; i < 128; i++ { + if f.Flag(i) { + s += string(rune(i)) + } + } + if w, ok := f.Width(); ok { + s += fmt.Sprintf("%d", w) + } + if p, ok := f.Precision(); ok { + s += fmt.Sprintf(".%d", p) + } + s += string(c) + io.WriteString(f, "["+s+"]") +} + +var flagtests = []struct { + in string + out string +}{ + {"%a", "[%a]"}, + {"%-a", "[%-a]"}, + {"%+a", "[%+a]"}, + {"%#a", "[%#a]"}, + {"% a", "[% a]"}, + {"%0a", "[%0a]"}, + {"%1.2a", "[%1.2a]"}, + {"%-1.2a", "[%-1.2a]"}, + {"%+1.2a", "[%+1.2a]"}, + {"%-+1.2a", "[%+-1.2a]"}, + {"%-+1.2abc", "[%+-1.2a]bc"}, + {"%-1.2abc", "[%-1.2a]bc"}, + {"%-0abc", "[%-0a]bc"}, +} + +func TestFlagParser(t *testing.T) { + var flagprinter flagPrinter + for _, tt := range flagtests { + s := fmt.Sprintf(tt.in, &flagprinter) + if s != tt.out { + t.Errorf("Sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out) + } + } +} + +func TestStructPrinter(t *testing.T) { + type T struct { + a string + b string + c int + } + var s T + s.a = "abc" + s.b = "def" + s.c = 123 + tests := []struct { + fmt string + out string + }{ + {"%v", "{abc def 123}"}, + {"%+v", "{a:abc b:def c:123}"}, + {"%#v", `fmt_test.T{a:"abc", b:"def", c:123}`}, + } + for _, tt := range tests { + out := fmt.Sprintf(tt.fmt, s) + if out != tt.out { + t.Errorf("Sprintf(%q, s) = %#q, want %#q", tt.fmt, out, tt.out) + } + // The same but with a pointer. + out = fmt.Sprintf(tt.fmt, &s) + if out != "&"+tt.out { + t.Errorf("Sprintf(%q, &s) = %#q, want %#q", tt.fmt, out, "&"+tt.out) + } + } +} + +func TestSlicePrinter(t *testing.T) { + slice := []int{} + s := fmt.Sprint(slice) + if s != "[]" { + t.Errorf("empty slice printed as %q not %q", s, "[]") + } + slice = []int{1, 2, 3} + s = fmt.Sprint(slice) + if s != "[1 2 3]" { + t.Errorf("slice: got %q expected %q", s, "[1 2 3]") + } + s = fmt.Sprint(&slice) + if s != "&[1 2 3]" { + t.Errorf("&slice: got %q expected %q", s, "&[1 2 3]") + } +} + +// presentInMap checks map printing using substrings so we don't depend on the +// print order. +func presentInMap(s string, a []string, t *testing.T) { + for i := 0; i < len(a); i++ { + loc := strings.Index(s, a[i]) + if loc < 0 { + t.Errorf("map print: expected to find %q in %q", a[i], s) + } + // make sure the match ends here + loc += len(a[i]) + if loc >= len(s) || (s[loc] != ' ' && s[loc] != ']') { + t.Errorf("map print: %q not properly terminated in %q", a[i], s) + } + } +} + +func TestMapPrinter(t *testing.T) { + m0 := make(map[int]string) + s := fmt.Sprint(m0) + if s != "map[]" { + t.Errorf("empty map printed as %q not %q", s, "map[]") + } + m1 := map[int]string{1: "one", 2: "two", 3: "three"} + a := []string{"1:one", "2:two", "3:three"} + presentInMap(fmt.Sprintf("%v", m1), a, t) + presentInMap(fmt.Sprint(m1), a, t) + // Pointer to map prints the same but with initial &. + if !strings.HasPrefix(fmt.Sprint(&m1), "&") { + t.Errorf("no initial & for address of map") + } + presentInMap(fmt.Sprintf("%v", &m1), a, t) + presentInMap(fmt.Sprint(&m1), a, t) +} + +func TestEmptyMap(t *testing.T) { + const emptyMapStr = "map[]" + var m map[string]int + s := fmt.Sprint(m) + if s != emptyMapStr { + t.Errorf("nil map printed as %q not %q", s, emptyMapStr) + } + m = make(map[string]int) + s = fmt.Sprint(m) + if s != emptyMapStr { + t.Errorf("empty map printed as %q not %q", s, emptyMapStr) + } +} + +// TestBlank checks that Sprint (and hence Print, Fprint) puts spaces in the +// right places, that is, between arg pairs in which neither is a string. +func TestBlank(t *testing.T) { + got := fmt.Sprint("<", 1, ">:", 1, 2, 3, "!") + expect := "<1>:1 2 3!" + if got != expect { + t.Errorf("got %q expected %q", got, expect) + } +} + +// TestBlankln checks that Sprintln (and hence Println, Fprintln) puts spaces in +// the right places, that is, between all arg pairs. +func TestBlankln(t *testing.T) { + got := fmt.Sprintln("<", 1, ">:", 1, 2, 3, "!") + expect := "< 1 >: 1 2 3 !\n" + if got != expect { + t.Errorf("got %q expected %q", got, expect) + } +} + +// TestFormatterPrintln checks Formatter with Sprint, Sprintln, Sprintf. +func TestFormatterPrintln(t *testing.T) { + f := F(1) + expect := "\n" + s := fmt.Sprint(f, "\n") + if s != expect { + t.Errorf("Sprint wrong with Formatter: expected %q got %q", expect, s) + } + s = fmt.Sprintln(f) + if s != expect { + t.Errorf("Sprintln wrong with Formatter: expected %q got %q", expect, s) + } + s = fmt.Sprintf("%v\n", f) + if s != expect { + t.Errorf("Sprintf wrong with Formatter: expected %q got %q", expect, s) + } +} + +func args(a ...any) []any { return a } + +var startests = []struct { + fmt string + in []any + out string +}{ + {"%*d", args(4, 42), " 42"}, + {"%-*d", args(4, 42), "42 "}, + {"%*d", args(-4, 42), "42 "}, + {"%-*d", args(-4, 42), "42 "}, + {"%.*d", args(4, 42), "0042"}, + {"%*.*d", args(8, 4, 42), " 0042"}, + {"%0*d", args(4, 42), "0042"}, + // Some non-int types for width. (Issue 10732). + {"%0*d", args(uint(4), 42), "0042"}, + {"%0*d", args(uint64(4), 42), "0042"}, + {"%0*d", args('\x04', 42), "0042"}, + + // erroneous + {"%*d", args(nil, 42), "%!(BADWIDTH)42"}, + {"%*d", args(int(1e7), 42), "%!(BADWIDTH)42"}, + {"%*d", args(int(-1e7), 42), "%!(BADWIDTH)42"}, + {"%.*d", args(nil, 42), "%!(BADPREC)42"}, + {"%.*d", args(-1, 42), "%!(BADPREC)42"}, + {"%.*d", args(int(1e7), 42), "%!(BADPREC)42"}, + {"%.*d", args(uint(1e7), 42), "%!(BADPREC)42"}, + {"%.*d", args(uint64(1<<63), 42), "%!(BADPREC)42"}, // Huge negative (-inf). + {"%.*d", args(uint64(1<<64-1), 42), "%!(BADPREC)42"}, // Small negative (-1). + {"%*d", args(5, "foo"), "%!d(string= foo)"}, + {"%*% %d", args(20, 5), "% 5"}, + {"%*", args(4), "%!(NOVERB)"}, +} + +func TestWidthAndPrecision(t *testing.T) { + for i, tt := range startests { + s := fmt.Sprintf(tt.fmt, tt.in...) + if s != tt.out { + t.Errorf("#%d: %q: got %q expected %q", i, tt.fmt, s, tt.out) + } + } +} + +// PanicS is a type that panics in String. +type PanicS struct { + message any +} + +// Value receiver. +func (p PanicS) String() string { + panic(p.message) +} + +// PanicGo is a type that panics in GoString. +type PanicGo struct { + message any +} + +// Value receiver. +func (p PanicGo) GoString() string { + panic(p.message) +} + +// PanicF is a type that panics in Format. +type PanicF struct { + message any +} + +// Value receiver. +func (p PanicF) Format(f fmt.State, c rune) { + panic(p.message) +} + +var panictests = []struct { + fmt string + in any + out string +}{ + // String + {"%s", (*PanicS)(nil), ""}, // nil pointer special case + {"%s", PanicS{io.ErrUnexpectedEOF}, "%!s(PANIC=String method: unexpected EOF)"}, + {"%s", PanicS{3}, "%!s(PANIC=String method: 3)"}, + // GoString + {"%#v", (*PanicGo)(nil), ""}, // nil pointer special case + {"%#v", PanicGo{io.ErrUnexpectedEOF}, "%!v(PANIC=GoString method: unexpected EOF)"}, + {"%#v", PanicGo{3}, "%!v(PANIC=GoString method: 3)"}, + // Issue 18282. catchPanic should not clear fmtFlags permanently. + {"%#v", []any{PanicGo{3}, PanicGo{3}}, "[]interface {}{%!v(PANIC=GoString method: 3), %!v(PANIC=GoString method: 3)}"}, + // Format + {"%s", (*PanicF)(nil), ""}, // nil pointer special case + {"%s", PanicF{io.ErrUnexpectedEOF}, "%!s(PANIC=Format method: unexpected EOF)"}, + {"%s", PanicF{3}, "%!s(PANIC=Format method: 3)"}, +} + +func TestPanics(t *testing.T) { + for i, tt := range panictests { + s := fmt.Sprintf(tt.fmt, tt.in) + if s != tt.out { + t.Errorf("%d: %q: got %q expected %q", i, tt.fmt, s, tt.out) + } + } +} + +// recurCount tests that erroneous String routine doesn't cause fatal recursion. +var recurCount = 0 + +type Recur struct { + i int + failed *bool +} + +func (r *Recur) String() string { + if recurCount++; recurCount > 10 { + *r.failed = true + return "FAIL" + } + // This will call badVerb. Before the fix, that would cause us to recur into + // this routine to print %!p(value). Now we don't call the user's method + // during an error. + return fmt.Sprintf("recur@%p value: %d", r, r.i) +} + +func TestBadVerbRecursion(t *testing.T) { + failed := false + r := &Recur{3, &failed} + _ = fmt.Sprintf("recur@%p value: %d\n", &r, r.i) + if failed { + t.Error("fail with pointer") + } + failed = false + r = &Recur{4, &failed} + _ = fmt.Sprintf("recur@%p, value: %d\n", r, r.i) + if failed { + t.Error("fail with value") + } +} + +func hideFromVet(s string) string { return s } + +func TestNilDoesNotBecomeTyped(t *testing.T) { + type A struct{} + type B struct{} + var a *A = nil + var b B = B{} + got := fmt.Sprintf(hideFromVet("%s %s %s %s %s"), nil, a, nil, b, nil) + const expect = "%!s() %!s(*fmt_test.A=) %!s() {} %!s()" + if got != expect { + t.Errorf("expected:\n\t%q\ngot:\n\t%q", expect, got) + } +} + +var formatterFlagTests = []struct { + in string + val any + out string +}{ + // scalar values with the (unused by fmt) 'a' verb. + {"%a", flagPrinter{}, "[%a]"}, + {"%-a", flagPrinter{}, "[%-a]"}, + {"%+a", flagPrinter{}, "[%+a]"}, + {"%#a", flagPrinter{}, "[%#a]"}, + {"% a", flagPrinter{}, "[% a]"}, + {"%0a", flagPrinter{}, "[%0a]"}, + {"%1.2a", flagPrinter{}, "[%1.2a]"}, + {"%-1.2a", flagPrinter{}, "[%-1.2a]"}, + {"%+1.2a", flagPrinter{}, "[%+1.2a]"}, + {"%-+1.2a", flagPrinter{}, "[%+-1.2a]"}, + {"%-+1.2abc", flagPrinter{}, "[%+-1.2a]bc"}, + {"%-1.2abc", flagPrinter{}, "[%-1.2a]bc"}, + {"%-0abc", flagPrinter{}, "[%-0a]bc"}, + + // composite values with the 'a' verb + {"%a", [1]flagPrinter{}, "[[%a]]"}, + {"%-a", [1]flagPrinter{}, "[[%-a]]"}, + {"%+a", [1]flagPrinter{}, "[[%+a]]"}, + {"%#a", [1]flagPrinter{}, "[[%#a]]"}, + {"% a", [1]flagPrinter{}, "[[% a]]"}, + {"%0a", [1]flagPrinter{}, "[[%0a]]"}, + {"%1.2a", [1]flagPrinter{}, "[[%1.2a]]"}, + {"%-1.2a", [1]flagPrinter{}, "[[%-1.2a]]"}, + {"%+1.2a", [1]flagPrinter{}, "[[%+1.2a]]"}, + {"%-+1.2a", [1]flagPrinter{}, "[[%+-1.2a]]"}, + {"%-+1.2abc", [1]flagPrinter{}, "[[%+-1.2a]]bc"}, + {"%-1.2abc", [1]flagPrinter{}, "[[%-1.2a]]bc"}, + {"%-0abc", [1]flagPrinter{}, "[[%-0a]]bc"}, + + // simple values with the 'v' verb + {"%v", flagPrinter{}, "[%v]"}, + {"%-v", flagPrinter{}, "[%-v]"}, + {"%+v", flagPrinter{}, "[%+v]"}, + {"%#v", flagPrinter{}, "[%#v]"}, + {"% v", flagPrinter{}, "[% v]"}, + {"%0v", flagPrinter{}, "[%0v]"}, + {"%1.2v", flagPrinter{}, "[%1.2v]"}, + {"%-1.2v", flagPrinter{}, "[%-1.2v]"}, + {"%+1.2v", flagPrinter{}, "[%+1.2v]"}, + {"%-+1.2v", flagPrinter{}, "[%+-1.2v]"}, + {"%-+1.2vbc", flagPrinter{}, "[%+-1.2v]bc"}, + {"%-1.2vbc", flagPrinter{}, "[%-1.2v]bc"}, + {"%-0vbc", flagPrinter{}, "[%-0v]bc"}, + + // composite values with the 'v' verb. + {"%v", [1]flagPrinter{}, "[[%v]]"}, + {"%-v", [1]flagPrinter{}, "[[%-v]]"}, + {"%+v", [1]flagPrinter{}, "[[%+v]]"}, + {"%#v", [1]flagPrinter{}, "[1]fmt_test.flagPrinter{[%#v]}"}, + {"% v", [1]flagPrinter{}, "[[% v]]"}, + {"%0v", [1]flagPrinter{}, "[[%0v]]"}, + {"%1.2v", [1]flagPrinter{}, "[[%1.2v]]"}, + {"%-1.2v", [1]flagPrinter{}, "[[%-1.2v]]"}, + {"%+1.2v", [1]flagPrinter{}, "[[%+1.2v]]"}, + {"%-+1.2v", [1]flagPrinter{}, "[[%+-1.2v]]"}, + {"%-+1.2vbc", [1]flagPrinter{}, "[[%+-1.2v]]bc"}, + {"%-1.2vbc", [1]flagPrinter{}, "[[%-1.2v]]bc"}, + {"%-0vbc", [1]flagPrinter{}, "[[%-0v]]bc"}, +} + +func TestFormatterFlags(t *testing.T) { + for _, tt := range formatterFlagTests { + s := fmt.Sprintf(tt.in, tt.val) + if s != tt.out { + t.Errorf("Sprintf(%q, %T) = %q, want %q", tt.in, tt.val, s, tt.out) + } + } +} + +func TestParsenum(t *testing.T) { + testCases := []struct { + s string + start, end int + num int + isnum bool + newi int + }{ + {"a123", 0, 4, 0, false, 0}, + {"1234", 1, 1, 0, false, 1}, + {"123a", 0, 4, 123, true, 3}, + {"12a3", 0, 4, 12, true, 2}, + {"1234", 0, 4, 1234, true, 4}, + {"1a234", 1, 3, 0, false, 1}, + } + for _, tt := range testCases { + num, isnum, newi := fmt.Parsenum(tt.s, tt.start, tt.end) + if num != tt.num || isnum != tt.isnum || newi != tt.newi { + t.Errorf("parsenum(%q, %d, %d) = %d, %v, %d, want %d, %v, %d", tt.s, tt.start, tt.end, num, isnum, newi, tt.num, tt.isnum, tt.newi) + } + } +} + +// Test the various Append printers. The details are well tested above; +// here we just make sure the byte slice is updated. + +const ( + appendResult = "hello world, 23" + hello = "hello " +) + +func TestAppendf(t *testing.T) { + b := make([]byte, 100) + b = b[:copy(b, hello)] + got := fmt.Appendf(b, "world, %d", 23) + if string(got) != appendResult { + t.Fatalf("Appendf returns %q not %q", got, appendResult) + } + if &b[0] != &got[0] { + t.Fatalf("Appendf allocated a new slice") + } +} + +func TestAppend(t *testing.T) { + b := make([]byte, 100) + b = b[:copy(b, hello)] + got := fmt.Append(b, "world", ", ", 23) + if string(got) != appendResult { + t.Fatalf("Append returns %q not %q", got, appendResult) + } + if &b[0] != &got[0] { + t.Fatalf("Append allocated a new slice") + } +} + +func TestAppendln(t *testing.T) { + b := make([]byte, 100) + b = b[:copy(b, hello)] + got := fmt.Appendln(b, "world,", 23) + if string(got) != appendResult+"\n" { + t.Fatalf("Appendln returns %q not %q", got, appendResult+"\n") + } + if &b[0] != &got[0] { + t.Fatalf("Appendln allocated a new slice") + } +} diff --git a/gnovm/tests/stdlibs/fmt/format.gno b/gnovm/tests/stdlibs/fmt/format.gno new file mode 100644 index 00000000000..90e18cd6963 --- /dev/null +++ b/gnovm/tests/stdlibs/fmt/format.gno @@ -0,0 +1,598 @@ +// 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 fmt + +import ( + "strconv" + "unicode/utf8" +) + +const ( + ldigits = "0123456789abcdefx" + udigits = "0123456789ABCDEFX" +) + +const ( + signed = true + unsigned = false +) + +// flags placed in a separate struct for easy clearing. +type fmtFlags struct { + widPresent bool + precPresent bool + minus bool + plus bool + sharp bool + space bool + zero bool + + // For the formats %+v %#v, we set the plusV/sharpV flags + // and clear the plus/sharp flags since %+v and %#v are in effect + // different, flagless formats set at the top level. + plusV bool + sharpV bool +} + +// A fmt is the raw formatter used by Printf etc. +// It prints into a buffer that must be set up separately. +type fmt struct { + buf *buffer + + fmtFlags + + wid int // width + prec int // precision + + // intbuf is large enough to store %b of an int64 with a sign and + // avoids padding at the end of the struct on 32 bit architectures. + intbuf [68]byte +} + +func (f *fmt) clearflags() { + f.fmtFlags = fmtFlags{} + f.wid = 0 + f.prec = 0 +} + +func (f *fmt) init(buf *buffer) { + f.buf = buf + f.clearflags() +} + +// writePadding generates n bytes of padding. +func (f *fmt) writePadding(n int) { + if n <= 0 { // No padding bytes needed. + return + } + buf := *f.buf + oldLen := len(buf) + newLen := oldLen + n + // Make enough room for padding. + if newLen > cap(buf) { + buf = make(buffer, cap(buf)*2+n) + copy(buf, *f.buf) + } + // Decide which byte the padding should be filled with. + padByte := byte(' ') + // Zero padding is allowed only to the left. + if f.zero && !f.minus { + padByte = byte('0') + } + // Fill padding with padByte. + padding := buf[oldLen:newLen] + for i := range padding { + padding[i] = padByte + } + *f.buf = buf[:newLen] +} + +// pad appends b to f.buf, padded on left (!f.minus) or right (f.minus). +func (f *fmt) pad(b []byte) { + if !f.widPresent || f.wid == 0 { + f.buf.write(b) + return + } + width := f.wid - utf8.RuneCount(b) + if !f.minus { + // left padding + f.writePadding(width) + f.buf.write(b) + } else { + // right padding + f.buf.write(b) + f.writePadding(width) + } +} + +// padString appends s to f.buf, padded on left (!f.minus) or right (f.minus). +func (f *fmt) padString(s string) { + if !f.widPresent || f.wid == 0 { + f.buf.writeString(s) + return + } + width := f.wid - utf8.RuneCountInString(s) + if !f.minus { + // left padding + f.writePadding(width) + f.buf.writeString(s) + } else { + // right padding + f.buf.writeString(s) + f.writePadding(width) + } +} + +// fmtBoolean formats a boolean. +func (f *fmt) fmtBoolean(v bool) { + if v { + f.padString("true") + } else { + f.padString("false") + } +} + +// fmtUnicode formats a uint64 as "U+0078" or with f.sharp set as "U+0078 'x'". +func (f *fmt) fmtUnicode(u uint64) { + buf := f.intbuf[0:] + + // With default precision set the maximum needed buf length is 18 + // for formatting -1 with %#U ("U+FFFFFFFFFFFFFFFF") which fits + // into the already allocated intbuf with a capacity of 68 bytes. + prec := 4 + if f.precPresent && f.prec > 4 { + prec = f.prec + // Compute space needed for "U+" , number, " '", character, "'". + width := 2 + prec + 2 + utf8.UTFMax + 1 + if width > len(buf) { + buf = make([]byte, width) + } + } + + // Format into buf, ending at buf[i]. Formatting numbers is easier right-to-left. + i := len(buf) + + // For %#U we want to add a space and a quoted character at the end of the buffer. + if f.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) { + i-- + buf[i] = '\'' + i -= utf8.RuneLen(rune(u)) + utf8.EncodeRune(buf[i:], rune(u)) + i-- + buf[i] = '\'' + i-- + buf[i] = ' ' + } + // Format the Unicode code point u as a hexadecimal number. + for u >= 16 { + i-- + buf[i] = udigits[u&0xF] + prec-- + u >>= 4 + } + i-- + buf[i] = udigits[u] + prec-- + // Add zeros in front of the number until requested precision is reached. + for prec > 0 { + i-- + buf[i] = '0' + prec-- + } + // Add a leading "U+". + i-- + buf[i] = '+' + i-- + buf[i] = 'U' + + oldZero := f.zero + f.zero = false + f.pad(buf[i:]) + f.zero = oldZero +} + +// fmtInteger formats signed and unsigned integers. +func (f *fmt) fmtInteger(u uint64, base int, isSigned bool, verb rune, digits string) { + negative := isSigned && int64(u) < 0 + if negative { + u = -u + } + + buf := f.intbuf[0:] + // The already allocated f.intbuf with a capacity of 68 bytes + // is large enough for integer formatting when no precision or width is set. + if f.widPresent || f.precPresent { + // Account 3 extra bytes for possible addition of a sign and "0x". + width := 3 + f.wid + f.prec // wid and prec are always positive. + if width > len(buf) { + // We're going to need a bigger boat. + buf = make([]byte, width) + } + } + + // Two ways to ask for extra leading zero digits: %.3d or %03d. + // If both are specified the f.zero flag is ignored and + // padding with spaces is used instead. + prec := 0 + if f.precPresent { + prec = f.prec + // Precision of 0 and value of 0 means "print nothing" but padding. + if prec == 0 && u == 0 { + oldZero := f.zero + f.zero = false + f.writePadding(f.wid) + f.zero = oldZero + return + } + } else if f.zero && !f.minus && f.widPresent { // Zero padding is allowed only to the left. + prec = f.wid + if negative || f.plus || f.space { + prec-- // leave room for sign + } + } + + // Because printing is easier right-to-left: format u into buf, ending at buf[i]. + // We could make things marginally faster by splitting the 32-bit case out + // into a separate block but it's not worth the duplication, so u has 64 bits. + i := len(buf) + // Use constants for the division and modulo for more efficient code. + // Switch cases ordered by popularity. + switch base { + case 10: + for u >= 10 { + i-- + next := u / 10 + buf[i] = byte('0' + u - next*10) + u = next + } + case 16: + for u >= 16 { + i-- + buf[i] = digits[u&0xF] + u >>= 4 + } + case 8: + for u >= 8 { + i-- + buf[i] = byte('0' + u&7) + u >>= 3 + } + case 2: + for u >= 2 { + i-- + buf[i] = byte('0' + u&1) + u >>= 1 + } + default: + panic("fmt: unknown base; can't happen") + } + i-- + buf[i] = digits[u] + for i > 0 && prec > len(buf)-i { + i-- + buf[i] = '0' + } + + // Various prefixes: 0x, -, etc. + if f.sharp { + switch base { + case 2: + // Add a leading 0b. + i-- + buf[i] = 'b' + i-- + buf[i] = '0' + case 8: + if buf[i] != '0' { + i-- + buf[i] = '0' + } + case 16: + // Add a leading 0x or 0X. + i-- + buf[i] = digits[16] + i-- + buf[i] = '0' + } + } + if verb == 'O' { + i-- + buf[i] = 'o' + i-- + buf[i] = '0' + } + + if negative { + i-- + buf[i] = '-' + } else if f.plus { + i-- + buf[i] = '+' + } else if f.space { + i-- + buf[i] = ' ' + } + + // Left padding with zeros has already been handled like precision earlier + // or the f.zero flag is ignored due to an explicitly set precision. + oldZero := f.zero + f.zero = false + f.pad(buf[i:]) + f.zero = oldZero +} + +// truncateString truncates the string s to the specified precision, if present. +func (f *fmt) truncateString(s string) string { + if f.precPresent { + n := f.prec + for i := range s { + n-- + if n < 0 { + return s[:i] + } + } + } + return s +} + +// truncate truncates the byte slice b as a string of the specified precision, if present. +func (f *fmt) truncate(b []byte) []byte { + if f.precPresent { + n := f.prec + for i := 0; i < len(b); { + n-- + if n < 0 { + return b[:i] + } + wid := 1 + if b[i] >= utf8.RuneSelf { + _, wid = utf8.DecodeRune(b[i:]) + } + i += wid + } + } + return b +} + +// fmtS formats a string. +func (f *fmt) fmtS(s string) { + s = f.truncateString(s) + f.padString(s) +} + +// fmtBs formats the byte slice b as if it was formatted as string with fmtS. +func (f *fmt) fmtBs(b []byte) { + b = f.truncate(b) + f.pad(b) +} + +// fmtSbx formats a string or byte slice as a hexadecimal encoding of its bytes. +func (f *fmt) fmtSbx(s string, b []byte, digits string) { + length := len(b) + if b == nil { + // No byte slice present. Assume string s should be encoded. + length = len(s) + } + // Set length to not process more bytes than the precision demands. + if f.precPresent && f.prec < length { + length = f.prec + } + // Compute width of the encoding taking into account the f.sharp and f.space flag. + width := 2 * length + if width > 0 { + if f.space { + // Each element encoded by two hexadecimals will get a leading 0x or 0X. + if f.sharp { + width *= 2 + } + // Elements will be separated by a space. + width += length - 1 + } else if f.sharp { + // Only a leading 0x or 0X will be added for the whole string. + width += 2 + } + } else { // The byte slice or string that should be encoded is empty. + if f.widPresent { + f.writePadding(f.wid) + } + return + } + // Handle padding to the left. + if f.widPresent && f.wid > width && !f.minus { + f.writePadding(f.wid - width) + } + // Write the encoding directly into the output buffer. + buf := *f.buf + if f.sharp { + // Add leading 0x or 0X. + buf = append(buf, '0', digits[16]) + } + var c byte + for i := 0; i < length; i++ { + if f.space && i > 0 { + // Separate elements with a space. + buf = append(buf, ' ') + if f.sharp { + // Add leading 0x or 0X for each element. + buf = append(buf, '0', digits[16]) + } + } + if b != nil { + c = b[i] // Take a byte from the input byte slice. + } else { + c = s[i] // Take a byte from the input string. + } + // Encode each byte as two hexadecimal digits. + buf = append(buf, digits[c>>4], digits[c&0xF]) + } + *f.buf = buf + // Handle padding to the right. + if f.widPresent && f.wid > width && f.minus { + f.writePadding(f.wid - width) + } +} + +// fmtSx formats a string as a hexadecimal encoding of its bytes. +func (f *fmt) fmtSx(s, digits string) { + f.fmtSbx(s, nil, digits) +} + +// fmtBx formats a byte slice as a hexadecimal encoding of its bytes. +func (f *fmt) fmtBx(b []byte, digits string) { + f.fmtSbx("", b, digits) +} + +// fmtQ formats a string as a double-quoted, escaped Go string constant. +// If f.sharp is set a raw (backquoted) string may be returned instead +// if the string does not contain any control characters other than tab. +func (f *fmt) fmtQ(s string) { + s = f.truncateString(s) + if f.sharp && strconv.CanBackquote(s) { + f.padString("`" + s + "`") + return + } + buf := f.intbuf[:0] + if f.plus { + f.pad(strconv.AppendQuoteToASCII(buf, s)) + } else { + f.pad(strconv.AppendQuote(buf, s)) + } +} + +// fmtC formats an integer as a Unicode character. +// If the character is not valid Unicode, it will print '\ufffd'. +func (f *fmt) fmtC(c uint64) { + // Explicitly check whether c exceeds utf8.MaxRune since the conversion + // of a uint64 to a rune may lose precision that indicates an overflow. + r := rune(c) + if c > utf8.MaxRune { + r = utf8.RuneError + } + buf := f.intbuf[:0] + f.pad(utf8.AppendRune(buf, r)) +} + +// fmtQc formats an integer as a single-quoted, escaped Go character constant. +// If the character is not valid Unicode, it will print '\ufffd'. +func (f *fmt) fmtQc(c uint64) { + r := rune(c) + if c > utf8.MaxRune { + r = utf8.RuneError + } + buf := f.intbuf[:0] + if f.plus { + f.pad(strconv.AppendQuoteRuneToASCII(buf, r)) + } else { + f.pad(strconv.AppendQuoteRune(buf, r)) + } +} + +// fmtFloat formats a float64. It assumes that verb is a valid format specifier +// for strconv.AppendFloat and therefore fits into a byte. +func (f *fmt) fmtFloat(v float64, size int, verb rune, prec int) { + // Explicit precision in format specifier overrules default precision. + if f.precPresent { + prec = f.prec + } + // Format number, reserving space for leading + sign if needed. + num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size) + if num[1] == '-' || num[1] == '+' { + num = num[1:] + } else { + num[0] = '+' + } + // f.space means to add a leading space instead of a "+" sign unless + // the sign is explicitly asked for by f.plus. + if f.space && num[0] == '+' && !f.plus { + num[0] = ' ' + } + // Special handling for infinities and NaN, + // which don't look like a number so shouldn't be padded with zeros. + if num[1] == 'I' || num[1] == 'N' { + oldZero := f.zero + f.zero = false + // Remove sign before NaN if not asked for. + if num[1] == 'N' && !f.space && !f.plus { + num = num[1:] + } + f.pad(num) + f.zero = oldZero + return + } + // The sharp flag forces printing a decimal point for non-binary formats + // and retains trailing zeros, which we may need to restore. + if f.sharp && verb != 'b' { + digits := 0 + switch verb { + case 'v', 'g', 'G', 'x': + digits = prec + // If no precision is set explicitly use a precision of 6. + if digits == -1 { + digits = 6 + } + } + + // Buffer pre-allocated with enough room for + // exponent notations of the form "e+123" or "p-1023". + var tailBuf [6]byte + tail := tailBuf[:0] + + hasDecimalPoint := false + sawNonzeroDigit := false + // Starting from i = 1 to skip sign at num[0]. + for i := 1; i < len(num); i++ { + switch num[i] { + case '.': + hasDecimalPoint = true + case 'p', 'P': + tail = append(tail, num[i:]...) + num = num[:i] + case 'e', 'E': + if verb != 'x' && verb != 'X' { + tail = append(tail, num[i:]...) + num = num[:i] + break + } + fallthrough + default: + if num[i] != '0' { + sawNonzeroDigit = true + } + // Count significant digits after the first non-zero digit. + if sawNonzeroDigit { + digits-- + } + } + } + if !hasDecimalPoint { + // Leading digit 0 should contribute once to digits. + if len(num) == 2 && num[1] == '0' { + digits-- + } + num = append(num, '.') + } + for digits > 0 { + num = append(num, '0') + digits-- + } + num = append(num, tail...) + } + // We want a sign if asked for and if the sign is not positive. + if f.plus || num[0] != '+' { + // If we're zero padding to the left we want the sign before the leading zeros. + // Achieve this by writing the sign out and then padding the unsigned number. + // Zero padding is allowed only to the left. + if f.zero && !f.minus && f.widPresent && f.wid > len(num) { + f.buf.writeByte(num[0]) + f.writePadding(f.wid - len(num)) + f.buf.write(num[1:]) + return + } + f.pad(num) + return + } + // No sign to show and the number is positive; just print the unsigned number. + f.pad(num[1:]) +} diff --git a/gnovm/tests/stdlibs/fmt/print.gno b/gnovm/tests/stdlibs/fmt/print.gno new file mode 100644 index 00000000000..29749a72afd --- /dev/null +++ b/gnovm/tests/stdlibs/fmt/print.gno @@ -0,0 +1,1160 @@ +// 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 fmt + +import ( + "io" + "math" + "os" + "strconv" + "unicode/utf8" +) + +// Strings for use with buffer.WriteString. +// This is less overhead than using buffer.Write with byte arrays. +const ( + commaSpaceString = ", " + nilAngleString = "" + nilParenString = "(nil)" + nilString = "nil" + mapString = "map[" + percentBangString = "%!" + missingString = "(MISSING)" + badIndexString = "(BADINDEX)" + panicString = "(PANIC=" + extraString = "%!(EXTRA " + badWidthString = "%!(BADWIDTH)" + badPrecString = "%!(BADPREC)" + noVerbString = "%!(NOVERB)" + invReflectString = "" +) + +// State represents the printer state passed to custom formatters. +// It provides access to the [io.Writer] interface plus information about +// the flags and options for the operand's format specifier. +type State interface { + // Write is the function to call to emit formatted output to be printed. + Write(b []byte) (n int, err error) + // Width returns the value of the width option and whether it has been set. + Width() (wid int, ok bool) + // Precision returns the value of the precision option and whether it has been set. + Precision() (prec int, ok bool) + + // Flag reports whether the flag c, a character, has been set. + Flag(c int) bool +} + +// Formatter is implemented by any value that has a Format method. +// The implementation controls how [State] and rune are interpreted, +// and may call [Sprint] or [Fprint](f) etc. to generate its output. +type Formatter interface { + Format(f State, verb rune) +} + +// Stringer is implemented by any value that has a String method, +// which defines the “native” format for that value. +// The String method is used to print values passed as an operand +// to any format that accepts a string or to an unformatted printer +// such as [Print]. +type Stringer interface { + String() string +} + +// GoStringer is implemented by any value that has a GoString method, +// which defines the Go syntax for that value. +// The GoString method is used to print values passed as an operand +// to a %#v format. +type GoStringer interface { + GoString() string +} + +// FormatString returns a string representing the fully qualified formatting +// directive captured by the [State], followed by the argument verb. ([State] does not +// itself contain the verb.) The result has a leading percent sign followed by any +// flags, the width, and the precision. Missing flags, width, and precision are +// omitted. This function allows a [Formatter] to reconstruct the original +// directive triggering the call to Format. +func FormatString(state State, verb rune) string { + var tmp [16]byte // Use a local buffer. + b := append(tmp[:0], '%') + for _, c := range " +-#0" { // All known flags + if state.Flag(int(c)) { // The argument is an int for historical reasons. + b = append(b, byte(c)) + } + } + if w, ok := state.Width(); ok { + b = strconv.AppendInt(b, int64(w), 10) + } + if p, ok := state.Precision(); ok { + b = append(b, '.') + b = strconv.AppendInt(b, int64(p), 10) + } + b = utf8.AppendRune(b, verb) + return string(b) +} + +// Use simple []byte instead of bytes.Buffer to avoid large dependency. +type buffer []byte + +func (b *buffer) write(p []byte) { + *b = append(*b, p...) +} + +func (b *buffer) writeString(s string) { + *b = append(*b, s...) +} + +func (b *buffer) writeByte(c byte) { + *b = append(*b, c) +} + +func (b *buffer) writeRune(r rune) { + *b = utf8.AppendRune(*b, r) +} + +// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations. +type pp struct { + buf buffer + + // arg holds the current item, as an interface{}. + arg any + + // value is used instead of arg for reflect values. + value valueInfo + + // fmt is used to format basic items such as integers or strings. + fmt fmt + + // reordered records whether the format string used argument reordering. + reordered bool + // goodArgNum records whether the most recent reordering directive was valid. + goodArgNum bool + // panicking is set by catchPanic to avoid infinite panic, recover, panic, ... recursion. + panicking bool + // erroring is set when printing an error string to guard against calling handleMethods. + erroring bool + // wrapErrs is set when the format string may contain a %w verb. + wrapErrs bool + // wrappedErrs records the targets of the %w verb. + wrappedErrs []int +} + +// newPrinter allocates a new pp struct or grabs a cached one. +func newPrinter() *pp { + p := new(pp) + p.fmt.init(&p.buf) + return p +} + +func (p *pp) Width() (wid int, ok bool) { return p.fmt.wid, p.fmt.widPresent } + +func (p *pp) Precision() (prec int, ok bool) { return p.fmt.prec, p.fmt.precPresent } + +func (p *pp) Flag(b int) bool { + switch b { + case '-': + return p.fmt.minus + case '+': + return p.fmt.plus || p.fmt.plusV + case '#': + return p.fmt.sharp || p.fmt.sharpV + case ' ': + return p.fmt.space + case '0': + return p.fmt.zero + } + return false +} + +// Implement Write so we can call [Fprintf] on a pp (through [State]), for +// recursive use in custom verbs. +func (p *pp) Write(b []byte) (ret int, err error) { + p.buf.write(b) + return len(b), nil +} + +// Implement WriteString so that we can call [io.WriteString] +// on a pp (through state), for efficiency. +func (p *pp) WriteString(s string) (ret int, err error) { + p.buf.writeString(s) + return len(s), nil +} + +// These routines end in 'f' and take a format string. + +// Fprintf formats according to a format specifier and writes to w. +// It returns the number of bytes written and any write error encountered. +func Fprintf(w io.Writer, format string, a ...any) (n int, err error) { + p := newPrinter() + p.doPrintf(format, a) + n, err = w.Write(p.buf) + return +} + +// Printf formats according to a format specifier and writes to standard output. +// It returns the number of bytes written and any write error encountered. +func Printf(format string, a ...any) (n int, err error) { + return Fprintf(os.Stdout, format, a...) +} + +// Sprintf formats according to a format specifier and returns the resulting string. +func Sprintf(format string, a ...any) string { + p := newPrinter() + p.doPrintf(format, a) + s := string(p.buf) + return s +} + +// Appendf formats according to a format specifier, appends the result to the byte +// slice, and returns the updated slice. +func Appendf(b []byte, format string, a ...any) []byte { + p := newPrinter() + p.doPrintf(format, a) + b = append(b, p.buf...) + return b +} + +// These routines do not take a format string + +// Fprint formats using the default formats for its operands and writes to w. +// Spaces are added between operands when neither is a string. +// It returns the number of bytes written and any write error encountered. +func Fprint(w io.Writer, a ...any) (n int, err error) { + p := newPrinter() + p.doPrint(a) + n, err = w.Write(p.buf) + return +} + +// Print formats using the default formats for its operands and writes to standard output. +// Spaces are added between operands when neither is a string. +// It returns the number of bytes written and any write error encountered. +func Print(a ...any) (n int, err error) { + return Fprint(os.Stdout, a...) +} + +// Sprint formats using the default formats for its operands and returns the resulting string. +// Spaces are added between operands when neither is a string. +func Sprint(a ...any) string { + p := newPrinter() + p.doPrint(a) + s := string(p.buf) + return s +} + +// Append formats using the default formats for its operands, appends the result to +// the byte slice, and returns the updated slice. +func Append(b []byte, a ...any) []byte { + p := newPrinter() + p.doPrint(a) + b = append(b, p.buf...) + return b +} + +// These routines end in 'ln', do not take a format string, +// always add spaces between operands, and add a newline +// after the last operand. + +// Fprintln formats using the default formats for its operands and writes to w. +// Spaces are always added between operands and a newline is appended. +// It returns the number of bytes written and any write error encountered. +func Fprintln(w io.Writer, a ...any) (n int, err error) { + p := newPrinter() + p.doPrintln(a) + n, err = w.Write(p.buf) + return +} + +// Println formats using the default formats for its operands and writes to standard output. +// Spaces are always added between operands and a newline is appended. +// It returns the number of bytes written and any write error encountered. +func Println(a ...any) (n int, err error) { + return Fprintln(os.Stdout, a...) +} + +// Sprintln formats using the default formats for its operands and returns the resulting string. +// Spaces are always added between operands and a newline is appended. +func Sprintln(a ...any) string { + p := newPrinter() + p.doPrintln(a) + s := string(p.buf) + return s +} + +// Appendln formats using the default formats for its operands, appends the result +// to the byte slice, and returns the updated slice. Spaces are always added +// between operands and a newline is appended. +func Appendln(b []byte, a ...any) []byte { + p := newPrinter() + p.doPrintln(a) + b = append(b, p.buf...) + return b +} + +// tooLarge reports whether the magnitude of the integer is +// too large to be used as a formatting width or precision. +func tooLarge(x int) bool { + const max int = 1e6 + return x > max || x < -max +} + +// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present. +func parsenum(s string, start, end int) (num int, isnum bool, newi int) { + if start >= end { + return 0, false, end + } + for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ { + if tooLarge(num) { + return 0, false, end // Overflow; crazy long number most likely. + } + num = num*10 + int(s[newi]-'0') + isnum = true + } + return +} + +func (p *pp) unknownType(v valueInfo) { + if v.Kind == "nil" { + p.buf.writeString(nilAngleString) + return + } + p.buf.writeByte('?') + p.buf.writeString(typeString(v.Origin)) + p.buf.writeByte('?') +} + +func (p *pp) badVerb(verb rune) { + p.erroring = true + p.buf.writeString(percentBangString) + p.buf.writeRune(verb) + p.buf.writeByte('(') + switch { + case p.arg != nil: + p.buf.writeString(typeString(p.arg)) + p.buf.writeByte('=') + p.printArg(p.arg, 'v') + case p.value.Origin != nil: + p.buf.writeString(typeString(p.value.Origin)) + p.buf.writeByte('=') + p.printValue(p.value, 'v', 0) + default: + p.buf.writeString(nilAngleString) + } + p.buf.writeByte(')') + p.erroring = false +} + +func (p *pp) fmtBool(v bool, verb rune) { + switch verb { + case 't', 'v': + p.fmt.fmtBoolean(v) + default: + p.badVerb(verb) + } +} + +// fmt0x64 formats a uint64 in hexadecimal and prefixes it with 0x or +// not, as requested, by temporarily setting the sharp flag. +func (p *pp) fmt0x64(v uint64, leading0x bool) { + sharp := p.fmt.sharp + p.fmt.sharp = leading0x + p.fmt.fmtInteger(v, 16, unsigned, 'v', ldigits) + p.fmt.sharp = sharp +} + +// fmtInteger formats a signed or unsigned integer. +func (p *pp) fmtInteger(v uint64, isSigned bool, verb rune) { + switch verb { + case 'v': + if p.fmt.sharpV && !isSigned { + p.fmt0x64(v, true) + } else { + p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits) + } + case 'd': + p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits) + case 'b': + p.fmt.fmtInteger(v, 2, isSigned, verb, ldigits) + case 'o', 'O': + p.fmt.fmtInteger(v, 8, isSigned, verb, ldigits) + case 'x': + p.fmt.fmtInteger(v, 16, isSigned, verb, ldigits) + case 'X': + p.fmt.fmtInteger(v, 16, isSigned, verb, udigits) + case 'c': + p.fmt.fmtC(v) + case 'q': + p.fmt.fmtQc(v) + case 'U': + p.fmt.fmtUnicode(v) + default: + p.badVerb(verb) + } +} + +// fmtFloat formats a float. The default precision for each verb +// is specified as last argument in the call to fmt_float. +func (p *pp) fmtFloat(v float64, size int, verb rune) { + switch verb { + case 'v': + p.fmt.fmtFloat(v, size, 'g', -1) + case 'b', 'g', 'G', 'x', 'X': + p.fmt.fmtFloat(v, size, verb, -1) + case 'f', 'e', 'E': + p.fmt.fmtFloat(v, size, verb, 6) + case 'F': + p.fmt.fmtFloat(v, size, 'f', 6) + default: + p.badVerb(verb) + } +} + +func (p *pp) fmtString(v string, verb rune) { + switch verb { + case 'v': + if p.fmt.sharpV { + p.fmt.fmtQ(v) + } else { + p.fmt.fmtS(v) + } + case 's': + p.fmt.fmtS(v) + case 'x': + p.fmt.fmtSx(v, ldigits) + case 'X': + p.fmt.fmtSx(v, udigits) + case 'q': + p.fmt.fmtQ(v) + default: + p.badVerb(verb) + } +} + +func (p *pp) fmtBytes(v []byte, verb rune, typeString string) { + switch verb { + case 'v', 'd': + if p.fmt.sharpV { + p.buf.writeString(typeString) + if v == nil { + p.buf.writeString(nilParenString) + return + } + p.buf.writeByte('{') + for i, c := range v { + if i > 0 { + p.buf.writeString(commaSpaceString) + } + p.fmt0x64(uint64(c), true) + } + p.buf.writeByte('}') + } else { + p.buf.writeByte('[') + for i, c := range v { + if i > 0 { + p.buf.writeByte(' ') + } + p.fmt.fmtInteger(uint64(c), 10, unsigned, verb, ldigits) + } + p.buf.writeByte(']') + } + case 's': + p.fmt.fmtBs(v) + case 'x': + p.fmt.fmtBx(v, ldigits) + case 'X': + p.fmt.fmtBx(v, udigits) + case 'q': + p.fmt.fmtQ(string(v)) + default: + p.printValue(valueOf(v), verb, 0) + } +} + +func (p *pp) fmtPointer(value valueInfo, verb rune) { + var u uint64 + switch value.Kind { + case "func", "map", "pointer", "slice": + u = getAddr(value.Base) + default: + p.badVerb(verb) + return + } + + switch verb { + case 'v': + if p.fmt.sharpV { + p.buf.writeByte('(') + p.buf.writeString(typeString(value.Origin)) + p.buf.writeString(")(") + if u == 0 { + p.buf.writeString(nilString) + } else { + p.fmt0x64(uint64(u), true) + } + p.buf.writeByte(')') + } else { + if u == 0 { + p.fmt.padString(nilAngleString) + } else { + p.fmt0x64(uint64(u), !p.fmt.sharp) + } + } + case 'p': + p.fmt0x64(uint64(u), !p.fmt.sharp) + case 'b', 'o', 'd', 'x', 'X': + p.fmtInteger(uint64(u), unsigned, verb) + default: + p.badVerb(verb) + } +} + +func (p *pp) catchPanic(arg any, verb rune, method string) { + if err := recover(); err != nil { + // If it's a nil pointer, just say "". The likeliest causes are a + // Stringer that fails to guard against nil or a nil pointer for a + // value receiver, and in either case, "" is a nice result. + if v := valueOf(arg); v.Kind == "pointer" && getAddr(v.Base) == 0 { + p.buf.writeString(nilAngleString) + return + } + // Otherwise print a concise panic message. Most of the time the panic + // value will print itself nicely. + if p.panicking { + // Nested panics; the recursion in printArg cannot succeed. + panic(err) + } + + oldFlags := p.fmt.fmtFlags + // For this output we want default behavior. + p.fmt.clearflags() + + p.buf.writeString(percentBangString) + p.buf.writeRune(verb) + p.buf.writeString(panicString) + p.buf.writeString(method) + p.buf.writeString(" method: ") + p.panicking = true + p.printArg(err, 'v') + p.panicking = false + p.buf.writeByte(')') + + p.fmt.fmtFlags = oldFlags + } +} + +func (p *pp) handleMethods(verb rune) (handled bool) { + if p.erroring { + return + } + if verb == 'w' { + // It is invalid to use %w other than with Errorf or with a non-error arg. + _, ok := p.arg.(error) + if !ok || !p.wrapErrs { + p.badVerb(verb) + return true + } + // If the arg is a Formatter, pass 'v' as the verb to it. + verb = 'v' + } + + // Is it a Formatter? + if formatter, ok := p.arg.(Formatter); ok { + handled = true + defer p.catchPanic(p.arg, verb, "Format") + formatter.Format(p, verb) + return + } + + // If we're doing Go syntax and the argument knows how to supply it, take care of it now. + if p.fmt.sharpV { + if stringer, ok := p.arg.(GoStringer); ok { + handled = true + defer p.catchPanic(p.arg, verb, "GoString") + // Print the result of GoString unadorned. + p.fmt.fmtS(stringer.GoString()) + return + } + } else { + // If a string is acceptable according to the format, see if + // the value satisfies one of the string-valued interfaces. + // Println etc. set verb to %v, which is "stringable". + switch verb { + case 'v', 's', 'x', 'X', 'q': + // Is it an error or Stringer? + // The duplication in the bodies is necessary: + // setting handled and deferring catchPanic + // must happen before calling the method. + switch v := p.arg.(type) { + case error: + handled = true + defer p.catchPanic(p.arg, verb, "Error") + p.fmtString(v.Error(), verb) + return + + case Stringer: + handled = true + defer p.catchPanic(p.arg, verb, "String") + p.fmtString(v.String(), verb) + return + } + } + } + return false +} + +func (p *pp) printArg(arg any, verb rune) { + p.arg = arg + p.value = valueInfo{} + + if arg == nil { + switch verb { + case 'T', 'v': + p.fmt.padString(nilAngleString) + default: + p.badVerb(verb) + } + return + } + + // Special processing considerations. + // %T (the value's type) and %p (its address) are special; we always do them first. + switch verb { + case 'T': + p.fmt.fmtS(typeString(arg)) + return + case 'p': + p.fmtPointer(valueOf(arg), 'p') + return + } + + // Some types can be done without reflection. + switch f := arg.(type) { + case bool: + p.fmtBool(f, verb) + case float32: + p.fmtFloat(float64(f), 32, verb) + case float64: + p.fmtFloat(f, 64, verb) + case int: + p.fmtInteger(uint64(f), signed, verb) + case int8: + p.fmtInteger(uint64(f), signed, verb) + case int16: + p.fmtInteger(uint64(f), signed, verb) + case int32: + p.fmtInteger(uint64(f), signed, verb) + case int64: + p.fmtInteger(uint64(f), signed, verb) + case uint: + p.fmtInteger(uint64(f), unsigned, verb) + case uint8: + p.fmtInteger(uint64(f), unsigned, verb) + case uint16: + p.fmtInteger(uint64(f), unsigned, verb) + case uint32: + p.fmtInteger(uint64(f), unsigned, verb) + case uint64: + p.fmtInteger(f, unsigned, verb) + case string: + p.fmtString(f, verb) + case []byte: + p.fmtBytes(f, verb, "[]byte") + case valueInfo: + // Handle extractable values with special methods + // since printValue does not handle them at depth 0. + if f.Origin != nil { + p.arg = f.Origin + if p.handleMethods(verb) { + return + } + } + p.printValue(f, verb, 0) + default: + // If the type is not simple, it might have methods. + if !p.handleMethods(verb) { + // Need to use reflection, since the type had no + // interface methods that could be used for formatting. + p.printValue(valueOf(f), verb, 0) + } + } +} + +// printValue is similar to printArg but starts with a reflect value, not an interface{} value. +// It does not handle 'p' and 'T' verbs because these should have been already handled by printArg. +func (p *pp) printValue(value valueInfo, verb rune, depth int) { + // Handle values with special methods if not already handled by printArg (depth == 0). + if depth > 0 && value.Origin != nil { + p.arg = value.Origin + if p.handleMethods(verb) { + return + } + } + p.arg = nil + p.value = value + + switch f := value; value.Kind { + case "bool": + p.fmtBool(f.Bytes == 1, verb) + case "int", "int8", "int16", "int32", "int64": + p.fmtInteger(f.Bytes, signed, verb) + case "uint", "uint8", "uint16", "uint32", "uint64": + p.fmtInteger(f.Bytes, unsigned, verb) + case "float32": + p.fmtFloat(float64(math.Float32frombits(uint32(f.Bytes))), 32, verb) + case "float64": + p.fmtFloat(math.Float64frombits(uint64(f.Bytes)), 64, verb) + case "string": + p.fmtString(value.Base.(string), verb) + case "map": + if p.fmt.sharpV { + p.buf.writeString(typeString(f.Origin)) + if getAddr(f.Base) == 0 { + p.buf.writeString(nilParenString) + return + } + p.buf.writeByte('{') + } else { + p.buf.writeString(mapString) + } + keys, vals := mapKeyValues(f.Base) + for i, k := range keys { + if i > 0 { + if p.fmt.sharpV { + p.buf.writeString(commaSpaceString) + } else { + p.buf.writeByte(' ') + } + } + p.printValue(valueOf(k), verb, depth+1) + p.buf.writeByte(':') + p.printValue(valueOf(vals[i]), verb, depth+1) + } + if p.fmt.sharpV { + p.buf.writeByte('}') + } else { + p.buf.writeByte(']') + } + case "struct": + if p.fmt.sharpV { + p.buf.writeString(typeString(f.Origin)) + } + p.buf.writeByte('{') + for i := 0; i < f.Len; i++ { + if i > 0 { + if p.fmt.sharpV { + p.buf.writeString(commaSpaceString) + } else { + p.buf.writeByte(' ') + } + } + name, val := fieldByIndex(f.Base, i) + if (p.fmt.plusV || p.fmt.sharpV) && name != "" { + p.buf.writeString(name) + p.buf.writeByte(':') + } + // XXX: go uses getField, I don't think it's needed in our case but + // might need to do something similar for interface values. + p.printValue(valueOf(val), verb, depth+1) + } + p.buf.writeByte('}') + case "interface": + value := f.Base + if value == nil { + if p.fmt.sharpV { + p.buf.writeString(typeString(f.Origin)) + p.buf.writeString(nilParenString) + } else { + p.buf.writeString(nilAngleString) + } + } else { + p.printValue(valueOf(value), verb, depth+1) + } + case "array", "slice": + switch verb { + case 's', 'q', 'x', 'X': + if bs, ok := asByteSlice(f.Base); ok { + p.fmtBytes(bs, verb, typeString(f.Origin)) + return + } + } + if p.fmt.sharpV { + p.buf.writeString(typeString(f.Origin)) + if f.Kind == "slice" && getAddr(f.Base) == 0 { + p.buf.writeString(nilParenString) + return + } + p.buf.writeByte('{') + for i := 0; i < f.Len; i++ { + if i > 0 { + p.buf.writeString(commaSpaceString) + } + p.printValue(valueOf(arrayIndex(f.Base, i)), verb, depth+1) + } + p.buf.writeByte('}') + } else { + p.buf.writeByte('[') + for i := 0; i < f.Len; i++ { + if i > 0 { + p.buf.writeByte(' ') + } + p.printValue(valueOf(arrayIndex(f.Base, i)), verb, depth+1) + } + p.buf.writeByte(']') + } + case "pointer": + // pointer to array or slice or struct? ok at top level + // but not embedded (avoid loops) + if depth == 0 && getAddr(f.Base) != 0 { + switch a := valueOf(getPtrElem(f.Base)); a.Kind { + case "array", "slice", "struct", "map": + p.buf.writeByte('&') + p.printValue(a, verb, depth+1) + return + } + } + fallthrough + case "func": + p.fmtPointer(f, verb) + default: + p.unknownType(f) + } +} + +// intFromArg gets the argNumth element of a. On return, isInt reports whether the argument has integer type. +func intFromArg(a []any, argNum int) (num int, isInt bool, newArgNum int) { + newArgNum = argNum + if argNum < len(a) { + num, isInt = a[argNum].(int) // Almost always OK. + if !isInt { + // Work harder. + switch v := valueOf(a[argNum]); v.Kind { + case "int", "int8", "int16", "int32", "int64": + n := int64(v.Bytes) + num = int(n) + isInt = true + case "uint", "uint8", "uint16", "uint32", "uint64": + n := v.Bytes + if int64(n) >= 0 && uint64(int(n)) == n { + num = int(n) + isInt = true + } + default: + // Already 0, false. + } + } + newArgNum = argNum + 1 + if tooLarge(num) { + num = 0 + isInt = false + } + } + return +} + +// parseArgNumber returns the value of the bracketed number, minus 1 +// (explicit argument numbers are one-indexed but we want zero-indexed). +// The opening bracket is known to be present at format[0]. +// The returned values are the index, the number of bytes to consume +// up to the closing paren, if present, and whether the number parsed +// ok. The bytes to consume will be 1 if no closing paren is present. +func parseArgNumber(format string) (index int, wid int, ok bool) { + // There must be at least 3 bytes: [n]. + if len(format) < 3 { + return 0, 1, false + } + + // Find closing bracket. + for i := 1; i < len(format); i++ { + if format[i] == ']' { + width, ok, newi := parsenum(format, 1, i) + if !ok || newi != i { + return 0, i + 1, false + } + return width - 1, i + 1, true // arg numbers are one-indexed and skip paren. + } + } + return 0, 1, false +} + +// argNumber returns the next argument to evaluate, which is either the value of the passed-in +// argNum or the value of the bracketed integer that begins format[i:]. It also returns +// the new value of i, that is, the index of the next byte of the format to process. +func (p *pp) argNumber(argNum int, format string, i int, numArgs int) (newArgNum, newi int, found bool) { + if len(format) <= i || format[i] != '[' { + return argNum, i, false + } + p.reordered = true + index, wid, ok := parseArgNumber(format[i:]) + if ok && 0 <= index && index < numArgs { + return index, i + wid, true + } + p.goodArgNum = false + return argNum, i + wid, ok +} + +func (p *pp) badArgNum(verb rune) { + p.buf.writeString(percentBangString) + p.buf.writeRune(verb) + p.buf.writeString(badIndexString) +} + +func (p *pp) missingArg(verb rune) { + p.buf.writeString(percentBangString) + p.buf.writeRune(verb) + p.buf.writeString(missingString) +} + +func (p *pp) doPrintf(format string, a []any) { + end := len(format) + argNum := 0 // we process one argument per non-trivial format + afterIndex := false // previous item in format was an index like [3]. + p.reordered = false +formatLoop: + for i := 0; i < end; { + p.goodArgNum = true + lasti := i + for i < end && format[i] != '%' { + i++ + } + if i > lasti { + p.buf.writeString(format[lasti:i]) + } + if i >= end { + // done processing format string + break + } + + // Process one verb + i++ + + // Do we have flags? + p.fmt.clearflags() + simpleFormat: + for ; i < end; i++ { + c := format[i] + switch c { + case '#': + p.fmt.sharp = true + case '0': + p.fmt.zero = true + case '+': + p.fmt.plus = true + case '-': + p.fmt.minus = true + case ' ': + p.fmt.space = true + default: + // Fast path for common case of ascii lower case simple verbs + // without precision or width or argument indices. + if 'a' <= c && c <= 'z' && argNum < len(a) { + switch c { + case 'w': + p.wrappedErrs = append(p.wrappedErrs, argNum) + fallthrough + case 'v': + // Go syntax + p.fmt.sharpV = p.fmt.sharp + p.fmt.sharp = false + // Struct-field syntax + p.fmt.plusV = p.fmt.plus + p.fmt.plus = false + } + p.printArg(a[argNum], rune(c)) + argNum++ + i++ + continue formatLoop + } + // Format is more complex than simple flags and a verb or is malformed. + break simpleFormat + } + } + + // Do we have an explicit argument index? + argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) + + // Do we have width? + if i < end && format[i] == '*' { + i++ + p.fmt.wid, p.fmt.widPresent, argNum = intFromArg(a, argNum) + + if !p.fmt.widPresent { + p.buf.writeString(badWidthString) + } + + // We have a negative width, so take its value and ensure + // that the minus flag is set + if p.fmt.wid < 0 { + p.fmt.wid = -p.fmt.wid + p.fmt.minus = true + p.fmt.zero = false // Do not pad with zeros to the right. + } + afterIndex = false + } else { + p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end) + if afterIndex && p.fmt.widPresent { // "%[3]2d" + p.goodArgNum = false + } + } + + // Do we have precision? + if i+1 < end && format[i] == '.' { + i++ + if afterIndex { // "%[3].2d" + p.goodArgNum = false + } + argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) + if i < end && format[i] == '*' { + i++ + p.fmt.prec, p.fmt.precPresent, argNum = intFromArg(a, argNum) + // Negative precision arguments don't make sense + if p.fmt.prec < 0 { + p.fmt.prec = 0 + p.fmt.precPresent = false + } + if !p.fmt.precPresent { + p.buf.writeString(badPrecString) + } + afterIndex = false + } else { + p.fmt.prec, p.fmt.precPresent, i = parsenum(format, i, end) + if !p.fmt.precPresent { + p.fmt.prec = 0 + p.fmt.precPresent = true + } + } + } + + if !afterIndex { + argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) + } + + if i >= end { + p.buf.writeString(noVerbString) + break + } + + verb, size := rune(format[i]), 1 + if verb >= utf8.RuneSelf { + verb, size = utf8.DecodeRuneInString(format[i:]) + } + i += size + + switch { + case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec. + p.buf.writeByte('%') + case !p.goodArgNum: + p.badArgNum(verb) + case argNum >= len(a): // No argument left over to print for the current verb. + p.missingArg(verb) + case verb == 'w': + p.wrappedErrs = append(p.wrappedErrs, argNum) + fallthrough + case verb == 'v': + // Go syntax + p.fmt.sharpV = p.fmt.sharp + p.fmt.sharp = false + // Struct-field syntax + p.fmt.plusV = p.fmt.plus + p.fmt.plus = false + fallthrough + default: + p.printArg(a[argNum], verb) + argNum++ + } + } + + // Check for extra arguments unless the call accessed the arguments + // out of order, in which case it's too expensive to detect if they've all + // been used and arguably OK if they're not. + if !p.reordered && argNum < len(a) { + p.fmt.clearflags() + p.buf.writeString(extraString) + for i, arg := range a[argNum:] { + if i > 0 { + p.buf.writeString(commaSpaceString) + } + if arg == nil { + p.buf.writeString(nilAngleString) + } else { + p.buf.writeString(typeString(arg)) + p.buf.writeByte('=') + p.printArg(arg, 'v') + } + } + p.buf.writeByte(')') + } +} + +func (p *pp) doPrint(a []any) { + prevString := false + for argNum, arg := range a { + isString := arg != nil && valueOf(arg).Kind == "string" + // Add a space between two non-string arguments. + if argNum > 0 && !isString && !prevString { + p.buf.writeByte(' ') + } + p.printArg(arg, 'v') + prevString = isString + } +} + +// doPrintln is like doPrint but always adds a space between arguments +// and a newline after the last argument. +func (p *pp) doPrintln(a []any) { + for argNum, arg := range a { + if argNum > 0 { + p.buf.writeByte(' ') + } + p.printArg(arg, 'v') + } + p.buf.writeByte('\n') +} + +// internal mini reflection implementation. +// ----------------------------------------------------------------------------- + +// native function to get a string representation of the type of v. +func typeString(v any) string + +type valueInfo struct { + // Original value passed to valueOf. + Origin any + // Kind of the value. + Kind string + // if Origin is of a DeclaredType, the name of the type. + DeclaredName string + // Byte value for simple scalar types (integers, booleans). + Bytes uint64 + // Base value stripped of the declared type. + Base any + // Length of the array, slice, string, struct, interface or map. + Len int +} + +func valueOf(v any) valueInfo { + k, dn, bytes, base, xlen := valueOfInternal(v) + return valueInfo{v, k, dn, bytes, base, xlen} +} + +func valueOfInternal(v any) (kind, declaredName string, bytes uint64, base any, xlen int) + +// numerical value of pointer. +func getAddr(v any) uint64 + +// dereference the pointer value at v. +func getPtrElem(v any) any + +// keys and values of the map. +func mapKeyValues(v any) ([]any, []any) + +// get the n'th element of the given array or slice. +func arrayIndex(v any, n int) any + +// n'th field of the given struct. +func fieldByIndex(v any, n int) (name string, value any) + +// asByteSlice converts v into a []byte. Argument can be a byte array, which +// would be converted, or a byte slice, where this function is idempotent. +func asByteSlice(v any) ([]byte, bool) diff --git a/gnovm/tests/stdlibs/fmt/print.go b/gnovm/tests/stdlibs/fmt/print.go new file mode 100644 index 00000000000..9d5a4efc725 --- /dev/null +++ b/gnovm/tests/stdlibs/fmt/print.go @@ -0,0 +1,266 @@ +package fmt + +import ( + "fmt" + "math" + "sort" + "unsafe" + + "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func X_typeString(v gnolang.TypedValue) string { + if v.IsUndefined() { + return "" + } + return v.T.String() +} + +func X_valueOfInternal(v gnolang.TypedValue) ( + kind, declaredName string, + bytes uint64, + base gnolang.TypedValue, + xlen int, +) { + if v.IsUndefined() { + kind = "nil" + return + } + if dt, ok := v.T.(*gnolang.DeclaredType); ok { + declaredName = dt.String() + } + baseT := gnolang.BaseOf(v.T) + base = gnolang.TypedValue{ + T: baseT, + V: v.V, + N: v.N, + } + switch baseT.Kind() { + case gnolang.BoolKind: + kind = "bool" + if v.GetBool() { + bytes = 1 + } + case gnolang.StringKind: + kind, xlen = "string", v.GetLength() + case gnolang.IntKind: + kind, bytes = "int", uint64(v.GetInt()) + case gnolang.Int8Kind: + kind, bytes = "int8", uint64(v.GetInt8()) + case gnolang.Int16Kind: + kind, bytes = "int16", uint64(v.GetInt16()) + case gnolang.Int32Kind: + kind, bytes = "int32", uint64(v.GetInt32()) + case gnolang.Int64Kind: + kind, bytes = "int64", uint64(v.GetInt64()) + case gnolang.UintKind: + kind, bytes = "uint", v.GetUint() + case gnolang.Uint8Kind: + kind, bytes = "uint8", uint64(v.GetUint8()) + case gnolang.Uint16Kind: + kind, bytes = "uint16", uint64(v.GetUint16()) + case gnolang.Uint32Kind: + kind, bytes = "uint32", uint64(v.GetUint32()) + case gnolang.Uint64Kind: + kind, bytes = "uint64", v.GetUint64() + case gnolang.Float32Kind: + kind, bytes = "float32", uint64(v.GetFloat32()) + case gnolang.Float64Kind: + kind, bytes = "float64", v.GetFloat64() + case gnolang.ArrayKind: + kind, xlen = "array", v.GetLength() + case gnolang.SliceKind: + kind, xlen = "slice", v.GetLength() + case gnolang.PointerKind: + kind = "pointer" + case gnolang.StructKind: + kind, xlen = "struct", len(baseT.(*gnolang.StructType).Fields) + case gnolang.InterfaceKind: + kind, xlen = "interface", len(baseT.(*gnolang.InterfaceType).Methods) + case gnolang.FuncKind: + kind = "func" + case gnolang.MapKind: + kind, xlen = "map", v.GetLength() + default: + panic("unexpected gnolang.Kind") + } + return +} + +func X_getAddr(m *gnolang.Machine, v gnolang.TypedValue) uint64 { + switch v.T.Kind() { + case gnolang.FuncKind, gnolang.MapKind, gnolang.SliceKind, gnolang.PointerKind: + default: + panic("invalid type") + } + switch v := v.V.(type) { + case nil: + return 0 + case gnolang.PointerValue: + if v.TV == nil { + return 0 + } + return uint64(uintptr(unsafe.Pointer(v.TV))) ^ + uint64(uintptr(unsafe.Pointer(&v.Base))) ^ + uint64(v.Index) ^ + uint64(uintptr(unsafe.Pointer(v.Key))) + case *gnolang.FuncValue: + return uint64(uintptr(unsafe.Pointer(v))) + case *gnolang.MapValue: + return uint64(uintptr(unsafe.Pointer(v))) + case *gnolang.SliceValue: + return uint64(uintptr(unsafe.Pointer(v.GetBase(m.Store)))) + default: + panic(fmt.Sprintf("unexpected value in getAddr: %T", v)) + } +} + +func X_getPtrElem(v gnolang.TypedValue) gnolang.TypedValue { + return v.V.(gnolang.PointerValue).Deref() +} + +var gSliceOfAny = &gnolang.SliceType{ + Elt: &gnolang.InterfaceType{}, +} + +func X_mapKeyValues(v gnolang.TypedValue) (keys, values gnolang.TypedValue) { + if v.T.Kind() != gnolang.MapKind { + panic(fmt.Sprintf("invalid arg to mapKeyValues of kind: %s", v.T.Kind())) + } + keys.T = gSliceOfAny + values.T = gSliceOfAny + if v.V == nil { + return + } + + mv := v.V.(*gnolang.MapValue) + ks, vs := make([]gnolang.TypedValue, 0, mv.GetLength()), make([]gnolang.TypedValue, 0, mv.GetLength()) + for el := mv.List.Head; el != nil; el = el.Next { + ks = append(ks, el.Key) + vs = append(vs, el.Value) + } + + // use stable to maintain the same order when we have weird map keys, + // like interfaces allowing for different concrete values. + sort.Stable(mapKV{ks, vs}) + + keys.V = &gnolang.SliceValue{ + Base: &gnolang.ArrayValue{ + List: ks, + }, + Length: len(ks), + Maxcap: len(ks), + } + values.V = &gnolang.SliceValue{ + Base: &gnolang.ArrayValue{ + List: vs, + }, + Length: len(vs), + Maxcap: len(vs), + } + return +} + +// mapKV is the map key values for sorting them, similarly to internal/fmtsort. +// it implements sort.Interface, as it has to work on two different slices. +type mapKV struct { + keys []gnolang.TypedValue + values []gnolang.TypedValue +} + +func (m mapKV) Len() int { return len(m.keys) } +func (m mapKV) Swap(i, j int) { + m.keys[i], m.keys[j] = m.keys[j], m.keys[i] + m.values[i], m.values[j] = m.values[j], m.values[i] +} + +func (m mapKV) Less(i, j int) bool { + ki, kj := m.keys[i], m.keys[j] + return compareKeys(ki, kj) +} + +func compareKeys(ki, kj gnolang.TypedValue) bool { + if ki.T.Kind() != kj.T.Kind() { + return false + } + switch ki.T.Kind() { + case gnolang.BoolKind: + bi, bj := ki.GetBool(), kj.GetBool() + // use == just to make it more explicit + return bi == false && bj == true + case gnolang.Float32Kind: + return math.Float32frombits(ki.GetFloat32()) < math.Float32frombits(kj.GetFloat32()) + case gnolang.Float64Kind: + return math.Float64frombits(ki.GetFloat64()) < math.Float64frombits(kj.GetFloat64()) + case gnolang.StringKind: + return ki.GetString() < kj.GetString() + case gnolang.IntKind, + gnolang.Int8Kind, + gnolang.Int16Kind, + gnolang.Int32Kind, + gnolang.Int64Kind: + return ki.ConvertGetInt() < kj.ConvertGetInt() + case gnolang.UintKind, + gnolang.Uint8Kind, + gnolang.Uint16Kind, + gnolang.Uint32Kind, + gnolang.Uint64Kind: + return uint64(ki.ConvertGetInt()) < uint64(kj.ConvertGetInt()) + default: + return false + } +} + +// get the n'th element of the given array or slice. +func X_arrayIndex(m *gnolang.Machine, v gnolang.TypedValue, n int) gnolang.TypedValue { + switch v.T.Kind() { + case gnolang.ArrayKind, gnolang.SliceKind: + tv := gnolang.TypedValue{T: gnolang.IntType} + tv.SetInt(int64(n)) + res := v.GetPointerAtIndex(m.Alloc, m.Store, &tv) + return res.Deref() + default: + panic("invalid type to arrayIndex") + } +} + +// n'th field of the given struct. +func X_fieldByIndex(v gnolang.TypedValue, n int) (name string, value gnolang.TypedValue) { + if v.T.Kind() != gnolang.StructKind { + panic("invalid kind to fieldByIndex") + } + fldType := v.T.(*gnolang.StructType).Fields[n] + name = string(fldType.Name) + value.T = fldType.Type + if v.V != nil { + value = v.V.(*gnolang.StructValue).Fields[n] + } + return +} + +func X_asByteSlice(v gnolang.TypedValue) (gnolang.TypedValue, bool) { + switch { + case v.T.Kind() == gnolang.SliceKind && v.T.Elem().Kind() == gnolang.Uint8Kind: + return gnolang.TypedValue{ + T: &gnolang.SliceType{ + Elt: gnolang.Uint8Type, + }, + V: v.V, + }, true + case v.T.Kind() == gnolang.ArrayKind && v.T.Elem().Kind() == gnolang.Uint8Kind: + arrt := v.T.(*gnolang.ArrayType) + return gnolang.TypedValue{ + T: &gnolang.SliceType{ + Elt: gnolang.Uint8Type, + }, + V: &gnolang.SliceValue{ + Base: v.V, + Offset: 0, + Length: arrt.Len, + Maxcap: arrt.Len, + }, + }, true + default: + return gnolang.TypedValue{}, false + } +} diff --git a/gnovm/tests/stdlibs/fmt/state_test.gno b/gnovm/tests/stdlibs/fmt/state_test.gno new file mode 100644 index 00000000000..fda660aa324 --- /dev/null +++ b/gnovm/tests/stdlibs/fmt/state_test.gno @@ -0,0 +1,80 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fmt_test + +import ( + "fmt" + "testing" +) + +type testState struct { + width int + widthOK bool + prec int + precOK bool + flag map[int]bool +} + +var _ fmt.State = testState{} + +func (s testState) Write(b []byte) (n int, err error) { + panic("unimplemented") +} + +func (s testState) Width() (wid int, ok bool) { + return s.width, s.widthOK +} + +func (s testState) Precision() (prec int, ok bool) { + return s.prec, s.precOK +} + +func (s testState) Flag(c int) bool { + return s.flag[c] +} + +const NO = -1000 + +func mkState(w, p int, flags string) testState { + s := testState{} + if w != NO { + s.width = w + s.widthOK = true + } + if p != NO { + s.prec = p + s.precOK = true + } + s.flag = make(map[int]bool) + for _, c := range flags { + s.flag[int(c)] = true + } + return s +} + +func TestFormatString(t *testing.T) { + var tests = []struct { + width, prec int + flags string + result string + }{ + {NO, NO, "", "%x"}, + {NO, 3, "", "%.3x"}, + {3, NO, "", "%3x"}, + {7, 3, "", "%7.3x"}, + {NO, NO, " +-#0", "% +-#0x"}, + {7, 3, "+", "%+7.3x"}, + {7, -3, "-", "%-7.-3x"}, + {7, 3, " ", "% 7.3x"}, + {7, 3, "#", "%#7.3x"}, + {7, 3, "0", "%07.3x"}, + } + for _, test := range tests { + got := fmt.FormatString(mkState(test.width, test.prec, test.flags), 'x') + if got != test.result { + t.Errorf("%v: got %s", test, got) + } + } +} diff --git a/gnovm/tests/stdlibs/fmt/stringer_test.gno b/gnovm/tests/stdlibs/fmt/stringer_test.gno new file mode 100644 index 00000000000..87bb13eb87e --- /dev/null +++ b/gnovm/tests/stdlibs/fmt/stringer_test.gno @@ -0,0 +1,61 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fmt_test + +import ( + "fmt" + "testing" +) + +type ( + TI int + TI8 int8 + TI16 int16 + TI32 int32 + TI64 int64 + TU uint + TU8 uint8 + TU16 uint16 + TU32 uint32 + TU64 uint64 + TF float64 + TF32 float32 + TF64 float64 + TB bool + TS string +) + +func (v TI) String() string { return fmt.Sprintf("I: %d", int(v)) } +func (v TI8) String() string { return fmt.Sprintf("I8: %d", int8(v)) } +func (v TI16) String() string { return fmt.Sprintf("I16: %d", int16(v)) } +func (v TI32) String() string { return fmt.Sprintf("I32: %d", int32(v)) } +func (v TI64) String() string { return fmt.Sprintf("I64: %d", int64(v)) } +func (v TU) String() string { return fmt.Sprintf("U: %d", uint(v)) } +func (v TU8) String() string { return fmt.Sprintf("U8: %d", uint8(v)) } +func (v TU16) String() string { return fmt.Sprintf("U16: %d", uint16(v)) } +func (v TU32) String() string { return fmt.Sprintf("U32: %d", uint32(v)) } +func (v TU64) String() string { return fmt.Sprintf("U64: %d", uint64(v)) } +func (v TF) String() string { return fmt.Sprintf("F: %f", float64(v)) } +func (v TF32) String() string { return fmt.Sprintf("F32: %f", float32(v)) } +func (v TF64) String() string { return fmt.Sprintf("F64: %f", float64(v)) } +func (v TB) String() string { return fmt.Sprintf("B: %t", bool(v)) } +func (v TS) String() string { return fmt.Sprintf("S: %q", string(v)) } + +func check(t *testing.T, got, want string) { + if got != want { + t.Error(got, "!=", want) + } +} + +func TestStringer(t *testing.T) { + s := fmt.Sprintf("%v %v %v %v %v", TI(0), TI8(1), TI16(2), TI32(3), TI64(4)) + check(t, s, "I: 0 I8: 1 I16: 2 I32: 3 I64: 4") + s = fmt.Sprintf("%v %v %v %v %v", TU(5), TU8(6), TU16(7), TU32(8), TU64(9)) + check(t, s, "U: 5 U8: 6 U16: 7 U32: 8 U64: 9") + s = fmt.Sprintf("%v %v %v", TF(1.0), TF32(2.0), TF64(3.0)) + check(t, s, "F: 1.000000 F32: 2.000000 F64: 3.000000") + s = fmt.Sprintf("%v %v", TB(true), TS("x")) + check(t, s, "B: true S: \"x\"") +} diff --git a/gnovm/tests/stdlibs/generated.go b/gnovm/tests/stdlibs/generated.go index db5ecdec05d..2262a91380a 100644 --- a/gnovm/tests/stdlibs/generated.go +++ b/gnovm/tests/stdlibs/generated.go @@ -1,4 +1,4 @@ -// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. +// Code generated by the genstd tool (@/misc/genstd); DO NOT EDIT. // To regenerate it, run `go generate` from this directory. package stdlibs @@ -7,6 +7,8 @@ import ( "reflect" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + testlibs_fmt "github.com/gnolang/gno/gnovm/tests/stdlibs/fmt" + testlibs_os "github.com/gnolang/gno/gnovm/tests/stdlibs/os" testlibs_std "github.com/gnolang/gno/gnovm/tests/stdlibs/std" testlibs_testing "github.com/gnolang/gno/gnovm/tests/stdlibs/testing" testlibs_unicode "github.com/gnolang/gno/gnovm/tests/stdlibs/unicode" @@ -32,30 +34,256 @@ func (n *NativeFunc) HasMachineParam() bool { var nativeFuncs = [...]NativeFunc{ { - "std", - "AssertOriginCall", - []gno.FieldTypeExpr{}, - []gno.FieldTypeExpr{}, + "fmt", + "typeString", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("any")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + p0 := *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV) + + r0 := testlibs_fmt.X_typeString(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "fmt", + "valueOfInternal", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("any")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r1"), Type: gno.X("string")}, + {Name: gno.N("r2"), Type: gno.X("uint64")}, + {Name: gno.N("r3"), Type: gno.X("any")}, + {Name: gno.N("r4"), Type: gno.X("int")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + p0 := *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV) + + r0, r1, r2, r3, r4 := testlibs_fmt.X_valueOfInternal(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(), + )) + m.PushValue(r3) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r4).Elem(), + )) + }, + }, + { + "fmt", + "getAddr", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("any")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("uint64")}, + }, true, func(m *gno.Machine) { - testlibs_std.AssertOriginCall( + b := m.LastBlock() + p0 := *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV) + + r0 := testlibs_fmt.X_getAddr( m, - ) + p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) }, }, { - "std", - "IsOriginCall", - []gno.FieldTypeExpr{}, + "fmt", + "getPtrElem", []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("p0"), Type: gno.X("any")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("any")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + p0 := *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV) + + r0 := testlibs_fmt.X_getPtrElem(p0) + + m.PushValue(r0) + }, + }, + { + "fmt", + "mapKeyValues", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("any")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("[]any")}, + {Name: gno.N("r1"), Type: gno.X("[]any")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + p0 := *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV) + + r0, r1 := testlibs_fmt.X_mapKeyValues(p0) + + m.PushValue(r0) + m.PushValue(r1) + }, + }, + { + "fmt", + "arrayIndex", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("any")}, + {Name: gno.N("p1"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("any")}, }, true, func(m *gno.Machine) { - r0 := testlibs_std.IsOriginCall( + b := m.LastBlock() + var ( + p0 = *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV) + p1 int + rp1 = reflect.ValueOf(&p1).Elem() + ) + + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + + r0 := testlibs_fmt.X_arrayIndex( m, + p0, p1) + + m.PushValue(r0) + }, + }, + { + "fmt", + "fieldByIndex", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("any")}, + {Name: gno.N("p1"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r1"), Type: gno.X("any")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 = *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV) + p1 int + rp1 = reflect.ValueOf(&p1).Elem() ) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + + r0, r1 := testlibs_fmt.X_fieldByIndex(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(r1) + }, + }, + { + "fmt", + "asByteSlice", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("any")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("[]byte")}, + {Name: gno.N("r1"), Type: gno.X("bool")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + p0 := *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV) + + r0, r1 := testlibs_fmt.X_asByteSlice(p0) + + m.PushValue(r0) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, + { + "os", + "write", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("[]byte")}, + {Name: gno.N("p1"), Type: gno.X("bool")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int")}, + }, + true, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 []byte + rp0 = reflect.ValueOf(&p0).Elem() + p1 bool + rp1 = reflect.ValueOf(&p1).Elem() + ) + + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + + r0 := testlibs_os.X_write( + m, + p0, p1) + m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, @@ -63,6 +291,18 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, + { + "std", + "AssertOriginCall", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + true, + func(m *gno.Machine) { + testlibs_std.AssertOriginCall( + m, + ) + }, + }, { "std", "TestSkipHeights", @@ -78,7 +318,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) testlibs_std.TestSkipHeights( m, @@ -102,7 +344,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := testlibs_std.X_callerAt( m, @@ -117,7 +361,7 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "testSetOrigCaller", + "testSetOriginCaller", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, @@ -130,16 +374,18 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) - testlibs_std.X_testSetOrigCaller( + testlibs_std.X_testSetOriginCaller( m, p0) }, }, { "std", - "testSetOrigPkgAddr", + "testSetOriginPkgAddr", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, @@ -152,9 +398,11 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) - testlibs_std.X_testSetOrigPkgAddr( + testlibs_std.X_testSetOriginPkgAddr( m, p0) }, @@ -177,8 +425,12 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) testlibs_std.X_testSetRealm( m, @@ -187,7 +439,7 @@ var nativeFuncs = [...]NativeFunc{ }, { "std", - "testSetOrigSend", + "testSetOriginSend", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("[]string")}, {Name: gno.N("p1"), Type: gno.X("[]int64")}, @@ -209,12 +461,20 @@ var nativeFuncs = [...]NativeFunc{ rp3 = reflect.ValueOf(&p3).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) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) - - testlibs_std.X_testSetOrigSend( + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) + tv3 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV + tv3.DeepFill(m.Store) + gno.Gno2GoValue(tv3, rp3) + + testlibs_std.X_testSetOriginSend( m, p0, p1, p2, p3) }, @@ -240,9 +500,15 @@ var nativeFuncs = [...]NativeFunc{ 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) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + tv2 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV + tv2.DeepFill(m.Store) + gno.Gno2GoValue(tv2, rp2) testlibs_std.X_testIssueCoins( m, @@ -267,7 +533,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0, r1 := testlibs_std.X_getRealm( m, @@ -302,7 +570,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := testlibs_std.X_isRealm( m, @@ -333,6 +603,70 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, + { + "testing", + "matchString", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("r1"), Type: gno.X("error")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + ) + + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) + tv1 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV + tv1.DeepFill(m.Store) + gno.Gno2GoValue(tv1, rp1) + + r0, r1 := testlibs_testing.X_matchString(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, + { + "testing", + "recoverWithStacktrace", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.AnyT()}, + {Name: gno.N("r1"), Type: gno.X("string")}, + }, + true, + func(m *gno.Machine) { + r0, r1 := testlibs_testing.X_recoverWithStacktrace( + m, + ) + + m.PushValue(r0) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, { "unicode", "IsPrint", @@ -350,7 +684,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := testlibs_unicode.IsPrint(p0) @@ -378,7 +714,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := testlibs_unicode.IsGraphic(p0) @@ -406,7 +744,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := testlibs_unicode.SimpleFold(p0) @@ -434,7 +774,9 @@ var nativeFuncs = [...]NativeFunc{ rp0 = reflect.ValueOf(&p0).Elem() ) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + tv0 := b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV + tv0.DeepFill(m.Store) + gno.Gno2GoValue(tv0, rp0) r0 := testlibs_unicode.IsUpper(p0) @@ -447,11 +789,7 @@ var nativeFuncs = [...]NativeFunc{ }, } -var initOrder = [...]string{ - "std", - "testing", - "unicode", -} +var initOrder = [...]string{} // InitOrder returns the initialization order of the standard libraries. // This is calculated starting from the list of all standard libraries and diff --git a/gnovm/tests/stdlibs/os/os.gno b/gnovm/tests/stdlibs/os/os.gno new file mode 100644 index 00000000000..7162cd07b89 --- /dev/null +++ b/gnovm/tests/stdlibs/os/os.gno @@ -0,0 +1,22 @@ +package os + +type ( + stdoutWriter struct{} + stderrWriter struct{} +) + +var ( + Stdout = &stdoutWriter{} + Stderr = &stderrWriter{} +) + +func (w *stdoutWriter) Write(p []byte) (n int, err error) { + return write(p, false), nil +} + +func (w *stderrWriter) Write(p []byte) (n int, err error) { + return write(p, true), nil +} + +// native bindings +func write(p []byte, isStderr bool) int diff --git a/gnovm/tests/stdlibs/os/os.go b/gnovm/tests/stdlibs/os/os.go new file mode 100644 index 00000000000..74250ba48b1 --- /dev/null +++ b/gnovm/tests/stdlibs/os/os.go @@ -0,0 +1,16 @@ +package os + +import ( + "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func X_write(m *gnolang.Machine, p []byte, isStderr bool) int { + if isStderr { + if w, ok := m.Output.(interface{ StderrWrite(p []byte) (int, error) }); ok { + n, _ := w.StderrWrite(p) + return n + } + } + n, _ := m.Output.Write(p) + return n +} diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index dcb5a64dbb3..8f1cf27d734 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -1,23 +1,22 @@ package std func AssertOriginCall() // injected -func IsOriginCall() bool // injected func TestSkipHeights(count int64) // injected -func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } -func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } +func TestSetOriginCaller(addr Address) { testSetOriginCaller(string(addr)) } +func TestSetOriginPkgAddress(addr Address) { testSetOriginPkgAddr(string(addr)) } // TestSetRealm sets the realm for the current frame. // After calling TestSetRealm, calling CurrentRealm() in the test function will yield the value of -// rlm, while if a realm function is called, using PrevRealm() will yield rlm. +// rlm, while if a realm function is called, using PreviousRealm() will yield rlm. func TestSetRealm(rlm Realm) { testSetRealm(string(rlm.addr), rlm.pkgPath) } -func TestSetOrigSend(sent, spent Coins) { +func TestSetOriginSend(sent, spent Coins) { sentDenom, sentAmt := sent.expandNative() spentDenom, spentAmt := spent.expandNative() - testSetOrigSend(sentDenom, sentAmt, spentDenom, spentAmt) + testSetOriginSend(sentDenom, sentAmt, spentDenom, spentAmt) } func TestIssueCoins(addr Address, coins Coins) { @@ -25,14 +24,14 @@ func TestIssueCoins(addr Address, coins Coins) { testIssueCoins(string(addr), denom, amt) } -// GetCallerAt calls callerAt, which we overwrite +// CallerAt calls callerAt, which we overwrite func callerAt(n int) string // native bindings -func testSetOrigCaller(s string) -func testSetOrigPkgAddr(s string) +func testSetOriginCaller(s string) +func testSetOriginPkgAddr(s string) func testSetRealm(addr, pkgPath string) -func testSetOrigSend( +func testSetOriginSend( sentDenom []string, sentAmt []int64, spentDenom []string, spentAmt []int64) func testIssueCoins(addr string, denom []string, amt []int64) diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 675194b252f..b6fcf23f048 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -14,7 +14,7 @@ import ( type TestExecContext struct { std.ExecContext - // These are used to set up the result of CurrentRealm() and PrevRealm(). + // These are used to set up the result of CurrentRealm() and PreviousRealm(). RealmFrames map[*gno.Frame]RealmOverride } @@ -26,7 +26,7 @@ type RealmOverride struct { } func AssertOriginCall(m *gno.Machine) { - if !IsOriginCall(m) { + if !isOriginCall(m) { m.Panic(typedString("invalid non-origin call")) } } @@ -37,7 +37,7 @@ func typedString(s gno.StringValue) gno.TypedValue { return tv } -func IsOriginCall(m *gno.Machine) bool { +func isOriginCall(m *gno.Machine) bool { tname := m.Frames[0].Func.Name switch tname { case "main": // test is a _filetest @@ -71,10 +71,10 @@ func TestSkipHeights(m *gno.Machine, count int64) { func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { - m.Panic(typedString("GetCallerAt requires positive arg")) + m.Panic(typedString("CallerAt requires positive arg")) return "" } - // Add 1 to n to account for the GetCallerAt (gno fn) frame. + // Add 1 to n to account for the CallerAt (gno fn) frame. n++ if n > m.NumFrames()-1 { // NOTE: the last frame's LastPackage @@ -84,22 +84,22 @@ func X_callerAt(m *gno.Machine, n int) string { return "" } if n == m.NumFrames()-1 { - // This makes it consistent with GetOrigCaller and TestSetOrigCaller. + // This makes it consistent with OriginCaller and TestSetOriginCaller. ctx := m.Context.(*TestExecContext) - return string(ctx.OrigCaller) + return string(ctx.OriginCaller) } return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } -func X_testSetOrigCaller(m *gno.Machine, addr string) { +func X_testSetOriginCaller(m *gno.Machine, addr string) { ctx := m.Context.(*TestExecContext) - ctx.OrigCaller = crypto.Bech32Address(addr) + ctx.OriginCaller = crypto.Bech32Address(addr) m.Context = ctx } -func X_testSetOrigPkgAddr(m *gno.Machine, addr string) { +func X_testSetOriginPkgAddr(m *gno.Machine, addr string) { ctx := m.Context.(*TestExecContext) - ctx.OrigPkgAddr = crypto.Bech32Address(addr) + ctx.OriginPkgAddr = crypto.Bech32Address(addr) m.Context = ctx } @@ -160,22 +160,22 @@ func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { } } - // Fallback case: return OrigCaller. - return string(ctx.OrigCaller), "" + // Fallback case: return OriginCaller. + return string(ctx.OriginCaller), "" } func X_isRealm(m *gno.Machine, pkgPath string) bool { return gno.IsRealmPath(pkgPath) } -func X_testSetOrigSend(m *gno.Machine, +func X_testSetOriginSend(m *gno.Machine, sentDenom []string, sentAmt []int64, spentDenom []string, spentAmt []int64, ) { ctx := m.Context.(*TestExecContext) - ctx.OrigSend = std.CompactCoins(sentDenom, sentAmt) + ctx.OriginSend = std.CompactCoins(sentDenom, sentAmt) spent := std.CompactCoins(spentDenom, spentAmt) - ctx.OrigSendSpent = &spent + ctx.OriginSendSpent = &spent m.Context = ctx } diff --git a/gnovm/tests/stdlibs/stdlibs.go b/gnovm/tests/stdlibs/stdlibs.go index 92316bf41fd..8a42ee9d9d9 100644 --- a/gnovm/tests/stdlibs/stdlibs.go +++ b/gnovm/tests/stdlibs/stdlibs.go @@ -6,7 +6,7 @@ import ( "github.com/gnolang/gno/gnovm/stdlibs" ) -//go:generate go run github.com/gnolang/gno/misc/genstd +//go:generate go run github.com/gnolang/gno/misc/genstd -skip-init-order func NativeResolver(pkgPath string, name gno.Name) func(*gno.Machine) { for _, nf := range nativeFuncs { diff --git a/gnovm/tests/stdlibs/testing/native_testing.gno b/gnovm/tests/stdlibs/testing/native_testing.gno index 3076a679e77..4428c57b2e6 100644 --- a/gnovm/tests/stdlibs/testing/native_testing.gno +++ b/gnovm/tests/stdlibs/testing/native_testing.gno @@ -1,3 +1,7 @@ package testing func unixNano() int64 + +func matchString(pat, str string) (result bool, err error) + +func recoverWithStacktrace() (interface{}, string) diff --git a/gnovm/tests/stdlibs/testing/native_testing.go b/gnovm/tests/stdlibs/testing/native_testing.go index db681ad8a62..a5f715babdb 100644 --- a/gnovm/tests/stdlibs/testing/native_testing.go +++ b/gnovm/tests/stdlibs/testing/native_testing.go @@ -1,7 +1,28 @@ package testing -import "time" +import ( + "regexp" + "time" + + "github.com/gnolang/gno/gnovm/pkg/gnolang" +) func X_unixNano() int64 { return time.Now().UnixNano() } + +func X_matchString(pat, str string) (result bool, err error) { + var matchRe *regexp.Regexp + if matchRe, err = regexp.Compile(pat); err != nil { + return + } + return matchRe.MatchString(str), nil +} + +func X_recoverWithStacktrace(m *gnolang.Machine) (gnolang.TypedValue, string) { + exception := m.Recover() + if exception == nil { + return gnolang.TypedValue{}, "" + } + return exception.Value, exception.Stacktrace.String() +} diff --git a/go.mod b/go.mod index cd038e2ae65..e9d61b9fa08 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,16 @@ module github.com/gnolang/gno -go 1.22 +go 1.23 -toolchain go1.22.4 +toolchain go1.23.6 require ( dario.cat/mergo v1.0.1 - github.com/alecthomas/chroma/v2 v2.14.0 + github.com/alecthomas/chroma/v2 v2.15.0 github.com/btcsuite/btcd/btcec/v2 v2.3.4 github.com/btcsuite/btcd/btcutil v1.1.6 github.com/cockroachdb/apd/v3 v3.2.1 - github.com/cosmos/ledger-cosmos-go v0.13.3 + github.com/cosmos/ledger-cosmos-go v0.14.0 github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/fortytw2/leaktest v1.3.0 @@ -21,53 +21,55 @@ require ( github.com/pelletier/go-toml v1.9.5 github.com/peterbourgon/ff/v3 v3.4.0 github.com/pmezard/go-difflib v1.0.0 - github.com/rogpeppe/go-internal v1.12.0 + github.com/rogpeppe/go-internal v1.13.1 github.com/rs/cors v1.11.1 github.com/rs/xid v1.6.0 github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/yuin/goldmark v1.7.2 + github.com/valyala/bytebufferpool v1.0.0 + github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc go.etcd.io/bbolt v1.3.11 - go.opentelemetry.io/otel v1.29.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 - go.opentelemetry.io/otel/metric v1.29.0 - go.opentelemetry.io/otel/sdk v1.29.0 - go.opentelemetry.io/otel/sdk/metric v1.29.0 + go.opentelemetry.io/otel v1.34.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 + go.opentelemetry.io/otel/metric v1.34.0 + go.opentelemetry.io/otel/sdk v1.34.0 + go.opentelemetry.io/otel/sdk/metric v1.34.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 go.uber.org/zap/exp v0.3.0 - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.32.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 - golang.org/x/mod v0.20.0 - golang.org/x/net v0.28.0 - golang.org/x/sync v0.8.0 - golang.org/x/term v0.23.0 - golang.org/x/tools v0.24.0 - google.golang.org/protobuf v1.35.1 + golang.org/x/mod v0.22.0 + golang.org/x/net v0.34.0 + golang.org/x/sync v0.10.0 + golang.org/x/term v0.28.0 + golang.org/x/tools v0.29.0 + google.golang.org/protobuf v1.36.3 + gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/dlclark/regexp2 v1.11.0 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect ) diff --git a/go.sum b/go.sum index 9c4d20dbad6..5fd4cddd627 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= -github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= -github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc= +github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= @@ -40,8 +40,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 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= @@ -55,8 +55,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -91,8 +91,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -129,8 +129,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -144,13 +144,15 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.2 h1:NjGd7lO7zrUn/A7eKwn5PEOt4ONYGqpxSEeZuduvgxc= -github.com/yuin/goldmark v1.7.2/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -159,22 +161,24 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -186,22 +190,22 @@ go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -212,35 +216,35 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w 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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/misc/autocounterd/cmd/cmd_start.go b/misc/autocounterd/cmd/cmd_start.go index ecf70f750be..e5155bf0718 100644 --- a/misc/autocounterd/cmd/cmd_start.go +++ b/misc/autocounterd/cmd/cmd_start.go @@ -44,7 +44,7 @@ func NewStartCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "start", ShortUsage: "start [flags]", - ShortHelp: "Runs the linter for the specified packages", + ShortHelp: "Increments the counter in the specified realm at regular intervals", }, cfg, func(_ context.Context, args []string) error { @@ -68,15 +68,15 @@ func execStart(cfg *startCfg, args []string, io commands.IO) error { signer, err := gnoclient.SignerFromBip39(cfg.mnemonic, cfg.chainID, "", uint32(0), uint32(0)) if err != nil { - return err + return fmt.Errorf("failed to create signer: %w", err) } if err := signer.Validate(); err != nil { - return err + return fmt.Errorf("invalid signer: %w", err) } rpcClient, err := rpcclient.NewHTTPClient(cfg.rpcURL) if err != nil { - return err + return fmt.Errorf("failed to create RPC client: %w", err) } client := gnoclient.Client{ @@ -97,7 +97,7 @@ func execStart(cfg *startCfg, args []string, io commands.IO) error { }) if err != nil { - fmt.Printf("[ERROR] Failed to call Incr on %s, %+v\n", cfg.realmPath, err.Error()) + fmt.Printf("[ERROR] Failed to call Incr on %s: %v\n", cfg.realmPath, err) } else { fmt.Println("[INFO] Counter incremented with success") } diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 82e8b0081ce..7d9def2b3cc 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -1,8 +1,6 @@ module autocounterd -go 1.22 - -toolchain go1.22.4 +go 1.23.6 require github.com/gnolang/gno v0.0.0-00010101000000-000000000000 @@ -11,7 +9,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -20,38 +18,40 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/misc/autocounterd/go.sum b/misc/autocounterd/go.sum index bd88dd5d08c..6d6d87fa01a 100644 --- a/misc/autocounterd/go.sum +++ b/misc/autocounterd/go.sum @@ -32,8 +32,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 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= @@ -78,8 +78,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -114,8 +114,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -129,53 +129,57 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -185,35 +189,35 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/misc/devdeps/go.mod b/misc/devdeps/go.mod index d3b40b73b52..74ceda0abf0 100644 --- a/misc/devdeps/go.mod +++ b/misc/devdeps/go.mod @@ -1,62 +1,65 @@ module github.com/gnolang/gno/misc/devdeps -go 1.22.1 +go 1.23.0 + +toolchain go1.23.2 require ( github.com/campoy/embedmd v1.0.0 - github.com/golangci/golangci-lint v1.62.2 // sync with github action - golang.org/x/tools v0.27.0 - google.golang.org/protobuf v1.35.1 + github.com/golangci/golangci-lint v1.64.5 // sync with github action + golang.org/x/tools v0.30.0 + google.golang.org/protobuf v1.36.5 moul.io/testman v1.5.0 mvdan.cc/gofumpt v0.7.0 ) require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect - 4d63.com/gochecknoglobals v0.2.1 // indirect - github.com/4meepo/tagalign v1.3.4 // indirect + 4d63.com/gochecknoglobals v0.2.2 // indirect + github.com/4meepo/tagalign v1.4.2 // indirect github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.0.0 // indirect - github.com/Antonboom/nilnil v1.0.0 // indirect + github.com/Antonboom/nilnil v1.0.1 // indirect github.com/Antonboom/testifylint v1.5.2 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect - github.com/Crocmagnon/fatcontext v0.5.3 // indirect + github.com/Crocmagnon/fatcontext v0.7.1 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect - github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect - github.com/alecthomas/go-check-sumtype v0.2.0 // indirect + github.com/alecthomas/go-check-sumtype v0.3.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.5 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect + github.com/alingse/nilnesserr v0.1.2 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect - github.com/ashanbrown/makezero v1.1.1 // indirect + github.com/ashanbrown/makezero v1.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v4 v4.4.1 // indirect + github.com/bombsimon/wsl/v4 v4.5.0 // indirect github.com/breml/bidichk v0.3.2 // indirect github.com/breml/errchkjson v0.4.0 // indirect - github.com/butuzov/ireturn v0.3.0 // indirect - github.com/butuzov/mirror v1.2.0 // indirect - github.com/catenacyber/perfsprint v0.7.1 // indirect + github.com/butuzov/ireturn v0.3.1 // indirect + github.com/butuzov/mirror v1.3.0 // indirect + github.com/catenacyber/perfsprint v0.8.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect - github.com/ckaznocha/intrange v0.2.1 // indirect - github.com/curioswitch/go-reassign v0.2.0 // indirect + github.com/ckaznocha/intrange v0.3.0 // indirect + github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.5 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.8 // indirect - github.com/go-critic/go-critic v0.11.5 // indirect + github.com/ghostiam/protogetter v0.3.9 // indirect + github.com/go-critic/go-critic v0.12.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -65,134 +68,136 @@ require ( github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect + github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect - github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect + github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect github.com/golangci/misspell v0.6.0 // indirect - github.com/golangci/modinfo v0.3.4 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect - github.com/golangci/revgrep v0.5.3 // indirect + github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect - github.com/gostaticanalysis/comment v1.4.2 // indirect - github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect + github.com/gostaticanalysis/comment v1.5.0 // indirect + github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect + github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect - github.com/jjti/go-spancheck v0.6.2 // indirect - github.com/julz/importas v0.1.0 // indirect - github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect + github.com/jjti/go-spancheck v0.6.4 // indirect + github.com/julz/importas v0.2.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect github.com/kisielk/errcheck v1.8.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect - github.com/kyoh86/exportloopref v0.1.11 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect - github.com/ldez/gomoddirectives v0.2.4 // indirect - github.com/ldez/tagliatelle v0.5.0 // indirect + github.com/ldez/exptostd v0.4.1 // indirect + github.com/ldez/gomoddirectives v0.6.1 // indirect + github.com/ldez/grignotin v0.9.0 // indirect + github.com/ldez/tagliatelle v0.7.1 // indirect + github.com/ldez/usetesting v0.4.2 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/macabu/inamedparam v0.1.3 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.9 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect - github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/matoous/godox v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.5.1 // indirect + github.com/mgechev/revive v1.7.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.18.3 // indirect + github.com/nunnatsa/ginkgolinter v0.19.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/peterbourgon/ff/v3 v3.3.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.7.0 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect + github.com/peterbourgon/ff/v3 v3.4.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/polyfloyd/go-errorlint v1.7.1 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/raeperd/recvcheck v0.1.2 // indirect + github.com/raeperd/recvcheck v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect - github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect - github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect - github.com/securego/gosec/v2 v2.21.4 // indirect - github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect + github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect + github.com/securego/gosec/v2 v2.22.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.12.1 // indirect github.com/sonatard/noctx v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.12.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/viper v1.19.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect - github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect + github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - github.com/tdakkota/asciicheck v0.2.0 // indirect - github.com/tetafro/godot v1.4.18 // indirect - github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tdakkota/asciicheck v0.4.1 // indirect + github.com/tetafro/godot v1.5.0 // indirect + github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect github.com/timonwong/loggercheck v0.10.1 // indirect - github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect + github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect - github.com/ultraware/funlen v0.1.0 // indirect - github.com/ultraware/whitespace v0.1.1 // indirect - github.com/uudashr/gocognit v1.1.3 // indirect - github.com/uudashr/iface v1.2.1 // indirect + github.com/ultraware/funlen v0.2.0 // indirect + github.com/ultraware/whitespace v0.2.0 // indirect + github.com/uudashr/gocognit v1.2.0 // indirect + github.com/uudashr/iface v1.3.1 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect - github.com/yuin/goldmark v1.4.13 // indirect + github.com/yuin/goldmark v1.7.8 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.13.0 // indirect - go-simpler.org/sloglint v0.7.2 // indirect + go-simpler.org/sloglint v0.9.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect - go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.26.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.18.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/exp v0.0.0-20250215185904-eff6e970281f // indirect + golang.org/x/exp/typeparams v0.0.0-20250215185904-eff6e970281f // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.5.1 // indirect + honnef.co/go/tools v0.6.0 // indirect moul.io/banner v1.0.1 // indirect moul.io/motd v1.0.0 // indirect moul.io/u v1.27.0 // indirect - mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect + mvdan.cc/unparam v0.0.0-20250211232406-0e51248738fc // indirect ) diff --git a/misc/devdeps/go.sum b/misc/devdeps/go.sum index fcba3fba624..6a720aaf84d 100644 --- a/misc/devdeps/go.sum +++ b/misc/devdeps/go.sum @@ -1,139 +1,92 @@ 4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= 4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= -4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= -4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= -github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= +4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= +4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= +github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E= +github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= -github.com/Antonboom/nilnil v1.0.0 h1:n+v+B12dsE5tbAqRODXmEKfZv9j2KcTBrp+LkoM4HZk= -github.com/Antonboom/nilnil v1.0.0/go.mod h1:fDJ1FSFoLN6yoG65ANb1WihItf6qt9PJVTn/s2IrcII= +github.com/Antonboom/nilnil v1.0.1 h1:C3Tkm0KUxgfO4Duk3PM+ztPncTFlOf0b2qadmS0s4xs= +github.com/Antonboom/nilnil v1.0.1/go.mod h1:CH7pW2JsRNFgEh8B2UaPZTEPhCMuFowP/e8Udp9Nnb0= github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Crocmagnon/fatcontext v0.5.3 h1:zCh/wjc9oyeF+Gmp+V60wetm8ph2tlsxocgg/J0hOps= -github.com/Crocmagnon/fatcontext v0.5.3/go.mod h1:XoCQYY1J+XTfyv74qLXvNw4xFunr3L1wkopIIKG7wGM= +github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVUt54PjM= +github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= -github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= -github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= -github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= -github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= -github.com/alecthomas/go-check-sumtype v0.2.0 h1:Bo+e4DFf3rs7ME9w/0SU/g6nmzJaphduP8Cjiz0gbwY= -github.com/alecthomas/go-check-sumtype v0.2.0/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= -github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= -github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= +github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= +github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo= +github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= -github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= -github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= +github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= -github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= +github.com/bombsimon/wsl/v4 v4.5.0 h1:iZRsEvDdyhd2La0FVi5k6tYehpOR/R7qIUjmKk7N74A= +github.com/bombsimon/wsl/v4 v4.5.0/go.mod h1:NOQ3aLF4nD7N5YPXMruR6ZXDOAqLoM0GEpLwTdvmOSc= github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= -github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= -github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= -github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= -github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= +github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY= +github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= +github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= +github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= -github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= -github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/catenacyber/perfsprint v0.8.1 h1:bGOHuzHe0IkoGeY831RW4aSlt1lPRd3WRAScSWOaV7E= +github.com/catenacyber/perfsprint v0.8.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/ckaznocha/intrange v0.2.1 h1:M07spnNEQoALOJhwrImSrJLaxwuiQK+hA2DeajBlwYk= -github.com/ckaznocha/intrange v0.2.1/go.mod h1:7NEhVyf8fzZO5Ds7CRaqPEm52Ut83hsTiL5zbER/HYk= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd5ZY= +github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= -github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= +github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= +github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -142,30 +95,20 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.8 h1:LYcXbYvybUyTIxN2Mj9h6rHrDZBDwZloPoKctWrFyJY= -github.com/ghostiam/protogetter v0.3.8/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= -github.com/go-critic/go-critic v0.11.5 h1:TkDTOn5v7EEngMxu8KbuFqFR43USaaH8XRJLz1jhVYA= -github.com/go-critic/go-critic v0.11.5/go.mod h1:wu6U7ny9PiaHaZHcvMDmdysMqvDem162Rh3zWTrqk8M= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/ghostiam/protogetter v0.3.9 h1:j+zlLLWzqLay22Cz/aYwTHKQ88GE2DQ6GkWSYFOI4lQ= +github.com/ghostiam/protogetter v0.3.9/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/go-critic/go-critic v0.12.0 h1:iLosHZuye812wnkEz1Xu3aBwn5ocCPfc9yqmFG9pa6w= +github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4am5mB/VfFK64w= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= @@ -189,146 +132,86 @@ github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUN github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= -github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= +github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -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.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -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/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= -github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= -github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= -github.com/golangci/golangci-lint v1.62.2 h1:b8K5K9PN+rZN1+mKLtsZHz2XXS9aYKzQ9i25x3Qnxxw= -github.com/golangci/golangci-lint v1.62.2/go.mod h1:ILWWyeFUrctpHVGMa1dg2xZPKoMUTc5OIMgW7HZr34g= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= +github.com/golangci/golangci-lint v1.64.5 h1:5omC86XFBKXZgCrVdUWU+WNHKd+CWCxNx717KXnzKZY= +github.com/golangci/golangci-lint v1.64.5/go.mod h1:WZnwq8TF0z61h3jLQ7Sk5trcP7b3kUFxLD6l1ivtdvU= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= -github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= -github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= -github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= -github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= +github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= -github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= -github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= +github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= +github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= +github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= +github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= -github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= +github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= +github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= -github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= -github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= -github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= +github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= +github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= +github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= +github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= +github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= +github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -341,67 +224,63 @@ github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= -github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= -github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= -github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= -github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= -github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= +github.com/ldez/exptostd v0.4.1 h1:DIollgQ3LWZMp3HJbSXsdE2giJxMfjyHj3eX4oiD6JU= +github.com/ldez/exptostd v0.4.1/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= +github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= +github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= +github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= +github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= +github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= +github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= +github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA= +github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= +github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.5.1 h1:hE+QPeq0/wIzJwOphdVyUJ82njdd8Khp4fUIHGZHW3M= -github.com/mgechev/revive v1.5.1/go.mod h1:lC9AhkJIBs5zwx8wkudyHrU+IJkrEKmpCmGMnIJPk4o= +github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY= +github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.18.3 h1:WgS7X3zzmni3vwHSBhvSgqrRgUecN6PQUcfB0j1noDw= -github.com/nunnatsa/ginkgolinter v0.18.3/go.mod h1:BE1xyB/PNtXXG1azrvrqJW5eFH0hSRylNzFy8QHPwzs= +github.com/nunnatsa/ginkgolinter v0.19.0 h1:CnHRFAeBS3LdLI9h+Jidbcc5KH71GKOmaBZQk8Srnto= +github.com/nunnatsa/ginkgolinter v0.19.0/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -410,46 +289,28 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= -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/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/peterbourgon/ff/v3 v3.0.0/go.mod h1:UILIFjRH5a/ar8TjXYLTkIvSvekZqPm5Eb/qbGk6CT0= -github.com/peterbourgon/ff/v3 v3.3.0 h1:PaKe7GW8orVFh8Unb5jNHS+JZBwWUMa2se0HM6/BI24= -github.com/peterbourgon/ff/v3 v3.3.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +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/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/polyfloyd/go-errorlint v1.7.0 h1:Zp6lzCK4hpBDj8y8a237YK4EPrMXQWvOe3nGoH4pFrU= -github.com/polyfloyd/go-errorlint v1.7.0/go.mod h1:dGWKu85mGHnegQ2SWpEybFityCg3j7ZbwsVUxAOk9gY= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA= +github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= @@ -460,37 +321,35 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/raeperd/recvcheck v0.1.2 h1:SjdquRsRXJc26eSonWIo8b7IMtKD3OAT2Lb5G3ZX1+4= -github.com/raeperd/recvcheck v0.1.2/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= +github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= +github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGKbLGBPtR/8/oO74W6hmz0qE5q0z9aqSAewaaM= +github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= -github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= -github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= +github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= -github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= -github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= +github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= +github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.22.1 h1:IcBt3TpI5Y9VN1YlwjSpM2cHu0i3Iw52QM+PQeg7jN8= +github.com/securego/gosec/v2 v2.22.1/go.mod h1:4bb95X4Jz7VSEPdVjC0hD7C/yR6kdeUBvCPOy9gDQ0g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= @@ -499,31 +358,30 @@ github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= -github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= +github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= +github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -534,33 +392,33 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= -github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= -github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= +github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= +github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.18 h1:ouX3XGiziKDypbpXqShBfnNLTSjR8r3/HVzrtJ+bHlI= -github.com/tetafro/godot v1.4.18/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= +github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw= +github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= +github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= -github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+9hijLQ4= -github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= +github.com/tomarrell/wrapcheck/v2 v2.10.0 h1:SzRCryzy4IrAH7bVGG4cK40tNUhmVmMDuJujy4XwYDg= +github.com/tomarrell/wrapcheck/v2 v2.10.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= -github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= -github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= -github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= -github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= -github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= -github.com/uudashr/iface v1.2.1 h1:vHHyzAUmWZ64Olq6NZT3vg/z1Ws56kyPdBOd5kTXDF8= -github.com/uudashr/iface v1.2.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= +github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= +github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= +github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= +github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= +github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= +github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= +github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= +github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= @@ -570,184 +428,91 @@ github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+ github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= -go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= -go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE= +go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20250215185904-eff6e970281f h1:oFMYAjX0867ZD2jcNiLBrI9BdpmEkvPyi5YrBGXbamg= +golang.org/x/exp v0.0.0-20250215185904-eff6e970281f/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f h1:WTyX8eCCyfdqiPYkRGm0MqElSfYFH3yR1+rl/mct9sA= -golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/exp/typeparams v0.0.0-20250215185904-eff6e970281f h1:lwUSxjTFq2sP4q5JdTtCEuDDSl3udvTn2UEksv8OHFY= +golang.org/x/exp/typeparams v0.0.0-20250215185904-eff6e970281f/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -755,92 +520,49 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -848,110 +570,29 @@ golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0t golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -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/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -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.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -959,15 +600,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= -honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= +honnef.co/go/tools v0.6.0 h1:TAODvD3knlq75WCp2nyGJtT4LeRV/o7NN9nYPeVJXf8= +honnef.co/go/tools v0.6.0/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= moul.io/banner v1.0.1 h1:+WsemGLhj2pOajw2eR5VYjLhOIqs0XhIRYchzTyMLk0= moul.io/banner v1.0.1/go.mod h1:XwvIGKkhKRKyN1vIdmR5oaKQLIkMhkMqrsHpS94QzAU= moul.io/godev v1.7.0/go.mod h1:5lgSpI1oH7xWpLl2Ew/Nsgk8DiNM6FzN9WV9+lgW8RQ= @@ -980,8 +614,5 @@ moul.io/u v1.27.0 h1:rF0p184mludn2DzL0unA8Gf/mFWMBerdqOh8cyuQYzQ= moul.io/u v1.27.0/go.mod h1:ggYDXxUjoHpfDsMPD3STqkUZTyA741PZiQhSd+7kRnA= mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= -mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= -mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +mvdan.cc/unparam v0.0.0-20250211232406-0e51248738fc h1:mEpjEutR7Qjdis+HqGQNdsJY/uRbH/MnyGXzLKMhDFo= +mvdan.cc/unparam v0.0.0-20250211232406-0e51248738fc/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE= diff --git a/misc/docs/tools/indexparser/go.mod b/misc/docs/tools/indexparser/go.mod new file mode 100644 index 00000000000..7a813aa5db1 --- /dev/null +++ b/misc/docs/tools/indexparser/go.mod @@ -0,0 +1,8 @@ +module indexparser + +go 1.22.7 + +require ( + github.com/peterbourgon/ff/v3 v3.4.0 + github.com/yuin/goldmark v1.7.8 +) diff --git a/misc/docs/tools/indexparser/go.sum b/misc/docs/tools/indexparser/go.sum new file mode 100644 index 00000000000..cc9506875b7 --- /dev/null +++ b/misc/docs/tools/indexparser/go.sum @@ -0,0 +1,4 @@ +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/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= diff --git a/misc/docs/tools/indexparser/main.go b/misc/docs/tools/indexparser/main.go new file mode 100644 index 00000000000..8e5ae141754 --- /dev/null +++ b/misc/docs/tools/indexparser/main.go @@ -0,0 +1,232 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "strings" + + "github.com/peterbourgon/ff/v3" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/text" +) + +type ( + // CategoryLink defines a generated-index link. + CategoryLink struct { + Type string `json:"type"` + Slug string `json:"slug"` + } + + // Category represents a category in the sidebar. + Category struct { + Type string `json:"type"` + Label string `json:"label"` + Items []interface{} `json:"items,omitempty"` + Link *CategoryLink `json:"link,omitempty"` + Collapsed bool `json:"collapsed,omitempty"` + } + + // ExternalLink represents an external link. + ExternalLink struct { + Type string `json:"type"` + Label string `json:"label"` + Href string `json:"href"` + } + + // Sidebar is the final JSON export structure. + Sidebar struct { + Sidebar []interface{} `json:"tutorialSidebar"` + } + + // RootConfig holds the configuration (path to the README file). + RootConfig struct { + IndexPath string + } +) + +var ( + rootFlagSet = flag.NewFlagSet("parser", flag.ExitOnError) +) + +func parseRootConfig(args []string) (*RootConfig, error) { + var cfg RootConfig + rootFlagSet.StringVar(&cfg.IndexPath, "path", "", "path to the docs README index file") + err := ff.Parse(rootFlagSet, args) + if err != nil { + return nil, fmt.Errorf("unable to parse flags: %w", err) + } + return &cfg, nil +} + +func main() { + cfg, err := parseRootConfig(os.Args[1:]) + if err != nil { + panic(err) + } + parseAndOutput(cfg) +} + +// parseAndOutput traverses the root AST and, for each level 2 heading, +// collects all subsequent nodes (typically lists) until the next level 2 heading. +// These lists are merged into a section. Finally, the sidebar is built as an array +// with "README" as the first element and an array of sections as the second element. +func parseAndOutput(cfg *RootConfig) { + if cfg.IndexPath == "" { + panic("no index file path provided") + } + source, err := os.ReadFile(cfg.IndexPath) + if err != nil { + panic(err) + } + gm := goldmark.New() + rootNode := gm.Parser().Parse(text.NewReader(source)) + + var sections []interface{} + // Iterate over the root nodes. + for n := rootNode.FirstChild(); n != nil; n = n.NextSibling() { + // Look for a level 2 heading (##). + if heading, ok := n.(*ast.Heading); ok && heading.Level == 2 { + label := string(heading.Text(source)) + sec := &Category{ + Type: "category", + Label: label, + } + // Scan subsequent nodes until the next level 2 heading or end. + for m := n.NextSibling(); m != nil; m = m.NextSibling() { + // If another level 2 heading is found, stop collecting. + if h, ok := m.(*ast.Heading); ok && h.Level == 2 { + break + } + // If it's a list, process it and append its items to the section. + if list, ok := m.(*ast.List); ok { + items, err := processList(list, source, 1) + if err != nil { + panic(err) + } + sec.Items = append(sec.Items, items...) + } + } + sections = append(sections, sec) + } + } + + var sb Sidebar + // Build the final structure with "README" as the first element, + // then the sections array. + sb.Sidebar = []interface{}{"README", sections} + + res, err := json.MarshalIndent(&sb, "", " ") + if err != nil { + panic(err) + } + output := strings.NewReplacer( + `\u0026`, `&`, + `\u003c`, `<`, + `\u003e`, `>`, + ).Replace(string(res)) + fmt.Println(output) +} + +// processList processes a List node and returns its items (categories or links). +func processList(list *ast.List, source []byte, depth int) ([]interface{}, error) { + var items []interface{} + for li := list.FirstChild(); li != nil; li = li.NextSibling() { + if liItem, ok := li.(*ast.ListItem); ok { + item, err := processListItem(liItem, source, depth) + if err != nil { + return nil, err + } + if item != nil { + items = append(items, item) + } + } + } + return items, nil +} + +// processListItem processes a ListItem node: +// - Recursively searches for the first link in the ListItem's content (excluding sub-lists). +// - If a sub-list exists in the item, creates a category with a "generated-index" link pointing to the extracted link. +// - Otherwise, returns simply the link. +func processListItem(li *ast.ListItem, source []byte, depth int) (interface{}, error) { + var link *ast.Link + // Recursively search for a link in the children of the ListItem, ignoring sub-lists. + for child := li.FirstChild(); child != nil; child = child.NextSibling() { + if _, ok := child.(*ast.List); ok { + continue + } + link = findLink(child) + if link != nil { + break + } + } + if link == nil { + return nil, nil + } + label := string(link.Text(source)) + slug := string(link.Destination) + + // Check for the presence of a sub-list within the same ListItem. + var hasSublist bool + var sublist *ast.List + for child := li.FirstChild(); child != nil; child = child.NextSibling() { + if lst, ok := child.(*ast.List); ok { + hasSublist = true + sublist = lst + break + } + } + + if hasSublist { + cat := &Category{ + Type: "category", + Label: label, + Collapsed: depth >= 1, + } + cat.Link = &CategoryLink{ + Type: "generated-index", + Slug: slug, + } + subItems, err := processList(sublist, source, depth+1) + if err != nil { + return nil, err + } + cat.Items = subItems + return cat, nil + } + return extractLink(link, source), nil +} + +// findLink recursively searches a node for an ast.Link. +func findLink(n ast.Node) *ast.Link { + if l, ok := n.(*ast.Link); ok { + return l + } + for child := n.FirstChild(); child != nil; child = child.NextSibling() { + if found := findLink(child); found != nil { + return found + } + } + return nil +} + +// extractLink converts a Link node into an external link (if the URL starts with "http://" or "https://") +// or returns a string (the slug without extension). +func extractLink(n ast.Node, source []byte) interface{} { + astLink, ok := n.(*ast.Link) + if !ok { + return nil + } + link := string(astLink.Destination) + if strings.HasPrefix(link, "http://") || strings.HasPrefix(link, "https://") { + return ExternalLink{ + Type: "link", + Label: string(astLink.Text(source)), + Href: link, + } + } + return strings.Split(link, ".")[0] +} diff --git a/misc/docs-linter/errors.go b/misc/docs/tools/linter/errors.go similarity index 100% rename from misc/docs-linter/errors.go rename to misc/docs/tools/linter/errors.go diff --git a/misc/docs-linter/go.mod b/misc/docs/tools/linter/go.mod similarity index 60% rename from misc/docs-linter/go.mod rename to misc/docs/tools/linter/go.mod index e27b82ef2f5..be771c9a952 100644 --- a/misc/docs-linter/go.mod +++ b/misc/docs/tools/linter/go.mod @@ -5,19 +5,17 @@ go 1.22 toolchain go1.22.4 require ( - github.com/gnolang/gno v0.0.0-00010101000000-000000000000 + github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c github.com/stretchr/testify v1.9.0 - golang.org/x/sync v0.8.0 + golang.org/x/sync v0.7.0 mvdan.cc/xurls/v2 v2.5.0 ) -replace github.com/gnolang/gno => ../.. - require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/misc/docs-linter/go.sum b/misc/docs/tools/linter/go.sum similarity index 65% rename from misc/docs-linter/go.sum rename to misc/docs/tools/linter/go.sum index 4957bd0cc88..ab8c3cf7c48 100644 --- a/misc/docs-linter/go.sum +++ b/misc/docs/tools/linter/go.sum @@ -1,17 +1,19 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c h1:jtZ+oN8ZpBM0wYbcFH0B7NjFFzTFqZZmZellSSKtaCE= +github.com/gnolang/gno v0.0.0-20240516161351-0c9849a8ef0c/go.mod h1:YcZbtNIfXVn4jS1pSG8SeG5RVHjyI7FPS3GypZaXxCI= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/misc/docs-linter/jsx.go b/misc/docs/tools/linter/jsx.go similarity index 93% rename from misc/docs-linter/jsx.go rename to misc/docs/tools/linter/jsx.go index eb041a78386..d0307680a0c 100644 --- a/misc/docs-linter/jsx.go +++ b/misc/docs/tools/linter/jsx.go @@ -50,7 +50,7 @@ func lintJSX(filepathToJSX map[string][]string) (string, error) { found = true } - fmt.Fprintf(&output, ">>> %s (found in file: %s)\n", tag, filePath) + output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", tag, filePath)) } } diff --git a/misc/docs-linter/links.go b/misc/docs/tools/linter/links.go similarity index 88% rename from misc/docs-linter/links.go rename to misc/docs/tools/linter/links.go index e34d35d9f58..678d129a75a 100644 --- a/misc/docs-linter/links.go +++ b/misc/docs/tools/linter/links.go @@ -64,7 +64,7 @@ func extractLocalLinks(fileContent []byte) []string { return links } -func lintLocalLinks(filepathToLinks map[string][]string, docsPath string) (string, error) { +func lintLocalLinks(filepathToLinks map[string][]string) (string, error) { var ( found bool output bytes.Buffer @@ -72,15 +72,14 @@ func lintLocalLinks(filepathToLinks map[string][]string, docsPath string) (strin for filePath, links := range filepathToLinks { for _, link := range links { - path := filepath.Join(docsPath, filepath.Dir(filePath), link) - + path := filepath.Join(filepath.Dir(filePath), link) if _, err := os.Stat(path); err != nil { if !found { output.WriteString("Could not find files with the following paths:\n") found = true } - fmt.Fprintf(&output, ">>> %s (found in file: %s)\n", link, filePath) + output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", link, filePath)) } } } diff --git a/misc/docs-linter/main.go b/misc/docs/tools/linter/main.go similarity index 88% rename from misc/docs-linter/main.go rename to misc/docs/tools/linter/main.go index 5d7cdf37982..caf563d1879 100644 --- a/misc/docs-linter/main.go +++ b/misc/docs/tools/linter/main.go @@ -15,7 +15,8 @@ import ( ) type cfg struct { - docsPath string + docsPath string + treatUrlsAsErr bool } func main() { @@ -48,6 +49,13 @@ func (c *cfg) RegisterFlags(fs *flag.FlagSet) { "./", "path to dir to walk for .md files", ) + + fs.BoolVar( + &c.treatUrlsAsErr, + "treat-urls-as-err", + true, + "treat URL 404s as errors instead of warnings", + ) } func execLint(cfg *cfg, ctx context.Context) (string, error) { @@ -61,8 +69,8 @@ func execLint(cfg *cfg, ctx context.Context) (string, error) { } // Main buffer to write to the end user after linting - var output bytes.Buffer - fmt.Fprintf(&output, "Linting %s...\n", absPath) + var output bytes.Buffer + output.WriteString(fmt.Sprintf("Linting %s...\n", absPath)) // Find docs files to lint mdFiles, err := findFilePaths(cfg.docsPath) @@ -110,18 +118,16 @@ func execLint(cfg *cfg, ctx context.Context) (string, error) { }) g.Go(func() error { - res, err := lintURLs(filepathToURLs, ctx) - if err != nil { - writeLock.Lock() - output.WriteString(res) - writeLock.Unlock() - } + res, err := lintURLs(ctx, filepathToURLs, cfg.treatUrlsAsErr) + writeLock.Lock() + output.WriteString(res) + writeLock.Unlock() return err }) g.Go(func() error { - res, err := lintLocalLinks(filepathToLocalLink, cfg.docsPath) + res, err := lintLocalLinks(filepathToLocalLink) if err != nil { writeLock.Lock() output.WriteString(res) diff --git a/misc/docs-linter/main_test.go b/misc/docs/tools/linter/main_test.go similarity index 100% rename from misc/docs-linter/main_test.go rename to misc/docs/tools/linter/main_test.go diff --git a/misc/docs-linter/urls.go b/misc/docs/tools/linter/urls.go similarity index 82% rename from misc/docs-linter/urls.go rename to misc/docs/tools/linter/urls.go index 098d0a05524..bdfb8c828c3 100644 --- a/misc/docs-linter/urls.go +++ b/misc/docs/tools/linter/urls.go @@ -44,14 +44,14 @@ func extractUrls(fileContent []byte) []string { return urls } -func lintURLs(filepathToURLs map[string][]string, ctx context.Context) (string, error) { +func lintURLs(ctx context.Context, filepathToURLs map[string][]string, treatAsError bool) (string, error) { // Setup parallel checking for links g, _ := errgroup.WithContext(ctx) var ( - lock sync.Mutex - output bytes.Buffer - found bool + lock sync.Mutex + output bytes.Buffer + hasInvalidLinks bool ) for filePath, urls := range filepathToURLs { @@ -61,12 +61,12 @@ func lintURLs(filepathToURLs map[string][]string, ctx context.Context) (string, g.Go(func() error { if err := checkUrl(url); err != nil { lock.Lock() - if !found { + if !hasInvalidLinks { output.WriteString("Remote links that need checking:\n") - found = true + hasInvalidLinks = true } - fmt.Fprintf(&output, ">>> %s (found in file: %s)\n", url, filePath) + output.WriteString(fmt.Sprintf(">>> %s (found in file: %s)\n", url, filePath)) lock.Unlock() } @@ -80,7 +80,10 @@ func lintURLs(filepathToURLs map[string][]string, ctx context.Context) (string, return "", err } - if found { + if !treatAsError { + errFound404Links = nil + } + if hasInvalidLinks { return output.String(), errFound404Links } diff --git a/misc/genstd/genstd.go b/misc/genstd/genstd.go index 7c041af5a72..e6d483b7748 100644 --- a/misc/genstd/genstd.go +++ b/misc/genstd/genstd.go @@ -11,6 +11,7 @@ package main import ( + "flag" "fmt" "go/ast" "go/parser" @@ -24,10 +25,13 @@ import ( _ "embed" ) +var skipInitOrder = flag.Bool("skip-init-order", false, "skip generating packages initialization order.") + func main() { + flag.Parse() path := "." - if len(os.Args) > 1 { - path = os.Args[1] + if a := flag.Arg(0); a != "" { + path = a } if err := _main(path); err != nil { fmt.Fprintf(os.Stderr, "%+v\n", err) @@ -54,7 +58,10 @@ func _main(stdlibsPath string) error { // Link up each Gno function with its matching Go function. mappings := linkFunctions(pkgs) - initOrder := sortPackages(pkgs) + var initOrder []string + if !*skipInitOrder { + initOrder = sortPackages(pkgs) + } // Create generated file. f, err := os.Create(outputFile) diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index 8b70e2f512d..4c4ab97017d 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -35,8 +35,12 @@ func (mt mappingType) GoQualifiedName() string { return types.ExprString(mt.Type) } -func (mt mappingType) GnoType() string { - return types.ExprString(mt.Type) +func (mt mappingType) GnoTypeExpression() string { + s := types.ExprString(mt.Type) + if s == "interface{}" { + return "gno.AnyT()" + } + return fmt.Sprintf("gno.X(%q)", s) } func linkFunctions(pkgs []*pkgData) []mapping { diff --git a/misc/genstd/mapping_test.go b/misc/genstd/mapping_test.go index a3c3886c6c1..b690fa1a952 100644 --- a/misc/genstd/mapping_test.go +++ b/misc/genstd/mapping_test.go @@ -74,7 +74,7 @@ func Test_linkFunctions(t *testing.T) { // require, otherwise the following would panic require.Len(t, v.Params, 1) p := v.Params[0] - assert.Equal(t, "int", p.GnoType()) + assert.Equal(t, `gno.X("int")`, p.GnoTypeExpression()) assert.Equal(t, "int", p.GoQualifiedName()) assert.False(t, p.IsTypedValue) } else { @@ -84,7 +84,7 @@ func Test_linkFunctions(t *testing.T) { // require, otherwise the following would panic require.Len(t, v.Results, 1) p := v.Results[0] - assert.Equal(t, "int", p.GnoType()) + assert.Equal(t, `gno.X("int")`, p.GnoTypeExpression()) assert.Equal(t, "int", p.GoQualifiedName()) assert.False(t, p.IsTypedValue) } else { @@ -126,7 +126,7 @@ func Test_linkFunctions_TypedValue(t *testing.T) { assert.Len(t, mappings[0].Results, 0) _ = assert.Len(t, mappings[0].Params, 1) && assert.Equal(t, true, mappings[0].Params[0].IsTypedValue) && - assert.Equal(t, "struct{m1 map[string]interface{}}", mappings[0].Params[0].GnoType()) + assert.Equal(t, `gno.X("struct{m1 map[string]any}")`, mappings[0].Params[0].GnoTypeExpression()) assert.Equal(t, false, mappings[1].MachineParam) assert.Equal(t, "TVResult", mappings[1].GnoFunc) @@ -134,7 +134,7 @@ func Test_linkFunctions_TypedValue(t *testing.T) { assert.Len(t, mappings[1].Params, 0) _ = assert.Len(t, mappings[1].Results, 1) && assert.Equal(t, true, mappings[1].Results[0].IsTypedValue) && - assert.Equal(t, "interface{S() map[int]Banker}", mappings[1].Results[0].GnoType()) + assert.Equal(t, `gno.AnyT()`, mappings[1].Results[0].GnoTypeExpression()) assert.Equal(t, true, mappings[2].MachineParam) assert.Equal(t, "TVFull", mappings[2].GnoFunc) diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl index 2d714589ef6..b59f8f7fc8f 100644 --- a/misc/genstd/template.tmpl +++ b/misc/genstd/template.tmpl @@ -1,4 +1,4 @@ -// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. +// Code generated by the genstd tool (@/misc/genstd); DO NOT EDIT. // To regenerate it, run `go generate` from this directory. package stdlibs @@ -38,12 +38,12 @@ var nativeFuncs = [...]NativeFunc{ {{- /* TODO: set nil if empty */}} []gno.FieldTypeExpr{ {{- range $i, $p := $m.Params }} - {Name: gno.N("p{{ $i }}"), Type: gno.X({{ printf "%q" $p.GnoType }})}, + {Name: gno.N("p{{ $i }}"), Type: {{ $p.GnoTypeExpression -}} }, {{- end }} }, []gno.FieldTypeExpr{ {{- range $i, $r := $m.Results }} - {Name: gno.N("r{{ $i }}"), Type: gno.X({{ printf "%q" $r.GnoType }})}, + {Name: gno.N("r{{ $i }}"), Type: {{ $r.GnoTypeExpression -}} }, {{- end }} }, {{ if $m.MachineParam }}true{{ else }}false{{ end }}, @@ -53,7 +53,7 @@ var nativeFuncs = [...]NativeFunc{ var ( {{- range $pn, $pv := $m.Params -}} {{- if $pv.IsTypedValue }} - p{{ $pn }} = gno.NewValuePathBlock(1, {{ $pn }}, "")).TV + p{{ $pn }} = *(b.GetPointerTo(nil, gno.NewValuePathBlock(1, {{ $pn }}, "")).TV) {{- else }} p{{ $pn }} {{ $pv.GoQualifiedName }} rp{{ $pn }} = reflect.ValueOf(&p{{ $pn }}).Elem() @@ -63,7 +63,9 @@ var nativeFuncs = [...]NativeFunc{ {{ range $pn, $pv := $m.Params -}} {{- if not $pv.IsTypedValue }} - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, {{ $pn }}, "")).TV, rp{{ $pn }}) + tv{{ $pn }} := b.GetPointerTo(nil, gno.NewValuePathBlock(1, {{ $pn }}, "")).TV + tv{{ $pn }}.DeepFill(m.Store) + gno.Gno2GoValue(tv{{ $pn }}, rp{{ $pn }}) {{- end -}} {{ end }} {{- end }} diff --git a/misc/genstd/testdata/integration/generated.go.golden b/misc/genstd/testdata/integration/generated.go.golden index d0be334480f..049adf83e7c 100644 --- a/misc/genstd/testdata/integration/generated.go.golden +++ b/misc/genstd/testdata/integration/generated.go.golden @@ -1,4 +1,4 @@ -// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. +// Code generated by the genstd tool (@/misc/genstd); DO NOT EDIT. // To regenerate it, run `go generate` from this directory. package stdlibs diff --git a/misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno b/misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno index 3bba36774e3..6927a149b7c 100644 --- a/misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno +++ b/misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno @@ -4,8 +4,8 @@ type Banker interface { B() } -func TVParam(m struct{ m1 map[string]interface{} }) +func TVParam(m struct{ m1 map[string]any }) -func TVResult() interface{ S() map[int]Banker } +func TVResult() interface{} -func TVFull(map[Banker]map[string]interface{}) (n [86]map[string]bool) +func TVFull(map[Banker]map[string]any) (n [86]map[string]bool) diff --git a/misc/genstd/util.go b/misc/genstd/util.go index 025fe4b673e..1793e69c7b7 100644 --- a/misc/genstd/util.go +++ b/misc/genstd/util.go @@ -70,7 +70,7 @@ func findDirs() (gitRoot string, relPath string, err error) { } p := wd for { - if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { + if _, e := os.Stat(filepath.Join(p, ".git")); e == nil { // make relPath relative to the git root rp := strings.TrimPrefix(wd, p+string(filepath.Separator)) // normalize separator to / diff --git a/misc/loop/cmd/snapshotter.go b/misc/loop/cmd/snapshotter.go index 0173f9aad03..eef4be36d2a 100644 --- a/misc/loop/cmd/snapshotter.go +++ b/misc/loop/cmd/snapshotter.go @@ -18,7 +18,7 @@ import ( "github.com/docker/docker/client" "github.com/docker/go-connections/nat" "github.com/gnolang/tx-archive/backup" - "github.com/gnolang/tx-archive/backup/client/http" + "github.com/gnolang/tx-archive/backup/client/rpc" "github.com/gnolang/tx-archive/backup/writer/standard" ) @@ -202,6 +202,10 @@ func (s snapshotter) backupTXs(ctx context.Context, rpcURL string) error { cfg.FromBlock = 1 cfg.Watch = false + // We want to skip failed txs on the Portal Loop reset, + // because they might (unexpectedly) succeed + cfg.SkipFailedTx = true + instanceBackupFile, err := os.Create(s.instanceBackupFile) if err != nil { return err @@ -211,7 +215,7 @@ func (s snapshotter) backupTXs(ctx context.Context, rpcURL string) error { w := standard.NewWriter(instanceBackupFile) // Create the tx-archive backup service - c, err := http.NewClient(rpcURL) + c, err := rpc.NewHTTPClient(rpcURL) if err != nil { return fmt.Errorf("could not create tx-archive client, %w", err) } diff --git a/misc/loop/go.mod b/misc/loop/go.mod index fc2c5daac59..7d0252da82c 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -1,14 +1,12 @@ module loop -go 1.22 - -toolchain go1.22.4 +go 1.23.6 require ( - github.com/docker/docker v24.0.7+incompatible + github.com/docker/docker v25.0.6+incompatible github.com/docker/go-connections v0.4.0 github.com/gnolang/gno v0.1.0-nightly.20240627 - github.com/gnolang/tx-archive v0.4.2 + github.com/gnolang/tx-archive v0.5.0 github.com/prometheus/client_golang v1.17.0 github.com/sirupsen/logrus v1.9.3 ) @@ -24,11 +22,12 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/distribution/reference v0.5.0 // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -36,7 +35,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/moby/term v0.5.0 // indirect @@ -53,31 +52,35 @@ require ( github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/misc/loop/go.sum b/misc/loop/go.sum index 1ed786fb82d..c5aed820f5e 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -40,8 +40,10 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= -github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 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= @@ -55,21 +57,21 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg= +github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gnolang/tx-archive v0.4.2 h1:xBBqLLKY9riv9yxpQgVhItCWxIji2rX6xNFmCY1cEOQ= -github.com/gnolang/tx-archive v0.4.2/go.mod h1:AGUBGO+DCLuKL80a1GJRnpcJ5gxVd9L4jEJXQB9uXp4= +github.com/gnolang/tx-archive v0.5.0 h1:npM+TfM3ufF2nz1V6hq+RLkCklPbADRZXBjiyPxXVu4= +github.com/gnolang/tx-archive v0.5.0/go.mod h1:thbXpyYT57ITGABl3hH4ftLSdO8eXaPFPi5hl6jZ2UE= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -100,8 +102,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -156,8 +158,8 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -173,10 +175,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -185,36 +189,44 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -223,15 +235,15 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -243,15 +255,15 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -263,20 +275,20 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/misc/stdlib_diff/Makefile b/misc/stdlib_diff/Makefile index 439af22c586..32dcf95a2ec 100644 --- a/misc/stdlib_diff/Makefile +++ b/misc/stdlib_diff/Makefile @@ -1,7 +1,9 @@ all: clean gen +GOROOT_SAVE ?= $(shell go env GOROOT) + gen: - go run . -src $(GOROOT)/src -dst ../../gnovm/stdlibs -out ./stdlib_diff + go run . -src $(GOROOT_SAVE)/src -dst ../../gnovm/stdlibs -out ./stdlib_diff clean: rm -rf stdlib_diff diff --git a/misc/stdlib_diff/README.md b/misc/stdlib_diff/README.md index 32c3cbcd93d..47d05a0373b 100644 --- a/misc/stdlib_diff/README.md +++ b/misc/stdlib_diff/README.md @@ -1,6 +1,6 @@ # stdlibs_diff -stdlibs_diff is a tool that generates an html report indicating differences between gno standard libraries and go standrad libraries +stdlibs_diff is a tool that generates an html report indicating differences between gno standard libraries and go standard libraries. ## Usage @@ -27,4 +27,4 @@ Compare the `gno` standard libraries the `go` standard libraries ## Tips -An index.html is generated at the root of the report location. Utilize it to navigate easily through the report. \ No newline at end of file +An index.html is generated at the root of the report location. Utilize it to navigate easily through the report. diff --git a/misc/stdlib_diff/go.mod b/misc/stdlib_diff/go.mod index 4e200f56ebb..7ec537a127e 100644 --- a/misc/stdlib_diff/go.mod +++ b/misc/stdlib_diff/go.mod @@ -1,5 +1,5 @@ module github.com/gnolang/gno/misc/stdlib_diff -go 1.21.0 +go 1.23.6 require github.com/hexops/gotextdiff v1.0.3 diff --git a/tm2/Makefile b/tm2/Makefile index 0aaa63e5285..fd3aede0d4c 100644 --- a/tm2/Makefile +++ b/tm2/Makefile @@ -63,3 +63,8 @@ _test.pkg.others:; go test $(GOTEST_FLAGS) `go list ./pkg/... | grep -Ev 'pkg/( _test.pkg.amino:; go test $(GOTEST_FLAGS) ./pkg/amino/... _test.pkg.bft:; go test $(GOTEST_FLAGS) ./pkg/bft/... _test.pkg.db:; go test $(GOTEST_FLAGS) ./pkg/db/... ./pkg/iavl/benchmarks/... + +.PHONY: generate +generate: + go generate -x ./... + $(MAKE) fmt diff --git a/tm2/pkg/amino/README.md b/tm2/pkg/amino/README.md index b0e0d8baa30..2921d043e96 100644 --- a/tm2/pkg/amino/README.md +++ b/tm2/pkg/amino/README.md @@ -84,8 +84,8 @@ var Package = amino.RegisterPackage( ) ``` -You can still override global registrations with local \*amino.Codec state. -This is used by genproto.P3Context, which may help development while writing +You can still override global registrations with local `*amino.Codec` state. +This is used by `genproto.P3Context`, which may help development while writing migration scripts. Feedback welcome in the issues section. ## Unsupported types diff --git a/tm2/pkg/amino/amino.go b/tm2/pkg/amino/amino.go index 262f5d9a54e..29a67d70eb8 100644 --- a/tm2/pkg/amino/amino.go +++ b/tm2/pkg/amino/amino.go @@ -162,6 +162,17 @@ func GetTypeURL(o interface{}) string { return gcdc.GetTypeURL(o) } +// Returns a new TypeInfo instance. +// NOTE: it uses a new codec for security's sake. +// (*TypeInfo of gcdc should not be exposed) +// Therefore it may be inefficient. If you need efficiency, implement with a +// new method that takes as argument a non-global codec instance. +func GetTypeInfo(rt reflect.Type) (info *TypeInfo, err error) { + cdc := NewCodec().WithPBBindings().Autoseal() + ti, err := cdc.GetTypeInfo(rt) + return ti, err +} + // ---------------------------------------- // Typ3 @@ -219,7 +230,8 @@ func (cdc *Codec) MarshalSized(o interface{}) ([]byte, error) { cdc.doAutoseal() // Write the bytes here. - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) // Write the bz without length-prefixing. bz, err := cdc.Marshal(o) @@ -239,7 +251,7 @@ func (cdc *Codec) MarshalSized(o interface{}) ([]byte, error) { return nil, err } - return buf.Bytes(), nil + return copyBytes(buf.Bytes()), nil } // MarshalSizedWriter writes the bytes as would be returned from @@ -271,8 +283,8 @@ func (cdc *Codec) MarshalAnySized(o interface{}) ([]byte, error) { cdc.doAutoseal() // Write the bytes here. - buf := new(bytes.Buffer) - + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) // Write the bz without length-prefixing. bz, err := cdc.MarshalAny(o) if err != nil { @@ -291,7 +303,7 @@ func (cdc *Codec) MarshalAnySized(o interface{}) ([]byte, error) { return nil, err } - return buf.Bytes(), nil + return copyBytes(buf.Bytes()), nil } func (cdc *Codec) MustMarshalAnySized(o interface{}) []byte { @@ -357,7 +369,9 @@ func (cdc *Codec) MarshalReflect(o interface{}) ([]byte, error) { // Encode Amino:binary bytes. var bz []byte - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + rt := rv.Type() info, err := cdc.getTypeInfoWLock(rt) if err != nil { @@ -377,7 +391,7 @@ func (cdc *Codec) MarshalReflect(o interface{}) ([]byte, error) { if err = cdc.writeFieldIfNotEmpty(buf, 1, info, FieldOptions{}, FieldOptions{}, rv, writeEmpty); err != nil { return nil, err } - bz = buf.Bytes() + bz = copyBytes(buf.Bytes()) } else { // The passed in BinFieldNum is only relevant for when the type is to // be encoded unpacked (elements are Typ3_ByteLength). In that case, @@ -387,7 +401,7 @@ func (cdc *Codec) MarshalReflect(o interface{}) ([]byte, error) { if err != nil { return nil, err } - bz = buf.Bytes() + bz = copyBytes(buf.Bytes()) } // If bz is empty, prefer nil. if len(bz) == 0 { @@ -443,16 +457,23 @@ func (cdc *Codec) MarshalAny(o interface{}) ([]byte, error) { } // Encode as interface. - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) err = cdc.encodeReflectBinaryInterface(buf, iinfo, reflect.ValueOf(&ivar).Elem(), FieldOptions{}, true) if err != nil { return nil, err } - bz := buf.Bytes() + bz := copyBytes(buf.Bytes()) return bz, nil } +func copyBytes(bz []byte) []byte { + cp := make([]byte, len(bz)) + copy(cp, bz) + return cp +} + // Panics if error. func (cdc *Codec) MustMarshalAny(o interface{}) []byte { bz, err := cdc.MarshalAny(o) @@ -764,7 +785,8 @@ func (cdc *Codec) JSONMarshal(o interface{}) ([]byte, error) { return []byte("null"), nil } rt := rv.Type() - w := new(bytes.Buffer) + w := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(w) info, err := cdc.getTypeInfoWLock(rt) if err != nil { return nil, err @@ -772,7 +794,8 @@ func (cdc *Codec) JSONMarshal(o interface{}) ([]byte, error) { if err = cdc.encodeReflectJSON(w, info, rv, FieldOptions{}); err != nil { return nil, err } - return w.Bytes(), nil + + return copyBytes(w.Bytes()), nil } func (cdc *Codec) MarshalJSONAny(o interface{}) ([]byte, error) { @@ -802,12 +825,14 @@ func (cdc *Codec) MarshalJSONAny(o interface{}) ([]byte, error) { } // Encode as interface. - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + err = cdc.encodeReflectJSONInterface(buf, iinfo, reflect.ValueOf(&ivar).Elem(), FieldOptions{}) if err != nil { return nil, err } - bz := buf.Bytes() + bz := copyBytes(buf.Bytes()) return bz, nil } @@ -863,28 +888,36 @@ func (cdc *Codec) MarshalJSONIndent(o interface{}, prefix, indent string) ([]byt if err != nil { return nil, err } + var out bytes.Buffer - err = json.Indent(&out, bz, prefix, indent) - if err != nil { + if err := json.Indent(&out, bz, prefix, indent); err != nil { return nil, err } - return out.Bytes(), nil + return copyBytes(out.Bytes()), nil } // ---------------------------------------- // Other +// Given amino package `pi`, register it with the global codec. // NOTE: do not modify the result. func RegisterPackage(pi *pkg.Package) *Package { gcdc.RegisterPackage(pi) return pi } +// Create an unregistered amino package with args: +// - (gopkg string) The Go package path, e.g. "github.com/gnolang/gno/tm2/pkg/std" +// - (p3pkg string) The (shorter) Proto3 package path (no slashes), e.g. "std" +// - (dirname string) Package directory this is called from. Typical is to use `amino.GetCallersDirname()` func NewPackage(gopkg string, p3pkg string, dirname string) *Package { return pkg.NewPackage(gopkg, p3pkg, dirname) } -// NOTE: duplicated in pkg/pkg.go +// Get caller's package directory. +// Implementation uses `filepath.Dir(runtime.Caller(1))`. +// NOTE: duplicated in pkg/pkg.go; given what it does and how, +// both are probably needed. func GetCallersDirname() string { dirname := "" // derive from caller. _, filename, _, ok := runtime.Caller(1) diff --git a/tm2/pkg/amino/binary_encode.go b/tm2/pkg/amino/binary_encode.go index 426cc520604..45758329284 100644 --- a/tm2/pkg/amino/binary_encode.go +++ b/tm2/pkg/amino/binary_encode.go @@ -1,12 +1,13 @@ package amino import ( - "bytes" "encoding/binary" "errors" "fmt" "io" "reflect" + + "github.com/valyala/bytebufferpool" ) const beOptionByte = 0x01 @@ -209,6 +210,8 @@ func (cdc *Codec) encodeReflectBinary(w io.Writer, info *TypeInfo, rv reflect.Va return err } +var poolBytesBuffer = new(bytebufferpool.Pool) + func (cdc *Codec) encodeReflectBinaryInterface(w io.Writer, iinfo *TypeInfo, rv reflect.Value, fopts FieldOptions, bare bool, ) (err error) { @@ -250,7 +253,9 @@ func (cdc *Codec) encodeReflectBinaryInterface(w io.Writer, iinfo *TypeInfo, rv // For Proto3 compatibility, encode interfaces as google.protobuf.Any // Write field #1, TypeURL - buf := bytes.NewBuffer(nil) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + { fnum := uint32(1) err = encodeFieldNumberAndTyp3(buf, fnum, Typ3ByteLength) @@ -269,7 +274,9 @@ func (cdc *Codec) encodeReflectBinaryInterface(w io.Writer, iinfo *TypeInfo, rv { // google.protobuf.Any values must be a struct, or an unpacked list which // is indistinguishable from a struct. - buf2 := bytes.NewBuffer(nil) + buf2 := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf2) + if !cinfo.IsStructOrUnpacked(fopts) { writeEmpty := false // Encode with an implicit struct, with a single field with number 1. @@ -356,7 +363,8 @@ func (cdc *Codec) encodeReflectBinaryList(w io.Writer, info *TypeInfo, rv reflec // Proto3 byte-length prefixing incurs alloc cost on the encoder. // Here we incur it for unpacked form for ease of dev. - buf := bytes.NewBuffer(nil) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) // If elem is not already a ByteLength type, write in packed form. // This is a Proto wart due to Proto backwards compatibility issues. @@ -393,6 +401,9 @@ func (cdc *Codec) encodeReflectBinaryList(w io.Writer, info *TypeInfo, rv reflec einfo.Elem.ReprType.Type.Kind() != reflect.Uint8 && einfo.Elem.ReprType.GetTyp3(fopts) != Typ3ByteLength + elemBuf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(elemBuf) + // Write elems in unpacked form. for i := 0; i < rv.Len(); i++ { // Write elements as repeated fields of the parent struct. @@ -431,20 +442,21 @@ func (cdc *Codec) encodeReflectBinaryList(w io.Writer, info *TypeInfo, rv reflec // form) are represented as lists of implicit structs. if writeImplicit { // Write field key for Value field of implicit struct. - buf2 := new(bytes.Buffer) - err = encodeFieldNumberAndTyp3(buf2, 1, Typ3ByteLength) + + err = encodeFieldNumberAndTyp3(elemBuf, 1, Typ3ByteLength) if err != nil { return } // Write field value of implicit struct to buf2. efopts := fopts efopts.BinFieldNum = 0 // dontcare - err = cdc.encodeReflectBinary(buf2, einfo, derv, efopts, false, 0) + err = cdc.encodeReflectBinary(elemBuf, einfo, derv, efopts, false, 0) if err != nil { return } // Write implicit struct to buf. - err = EncodeByteSlice(buf, buf2.Bytes()) + err = EncodeByteSlice(buf, elemBuf.Bytes()) + elemBuf.Reset() if err != nil { return } @@ -497,7 +509,8 @@ func (cdc *Codec) encodeReflectBinaryStruct(w io.Writer, info *TypeInfo, rv refl // Proto3 incurs a cost in writing non-root structs. // Here we incur it for root structs as well for ease of dev. - buf := bytes.NewBuffer(nil) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) for _, field := range info.Fields { // Get type info for field. @@ -553,7 +566,7 @@ func encodeFieldNumberAndTyp3(w io.Writer, num uint32, typ Typ3) (err error) { } func (cdc *Codec) writeFieldIfNotEmpty( - buf *bytes.Buffer, + buf *bytebufferpool.ByteBuffer, fieldNum uint32, finfo *TypeInfo, structsFopts FieldOptions, // the wrapping struct's FieldOptions if any @@ -579,7 +592,7 @@ func (cdc *Codec) writeFieldIfNotEmpty( if !isWriteEmpty && lBeforeValue == lAfterValue-1 && buf.Bytes()[buf.Len()-1] == 0x00 { // rollback typ3/fieldnum and last byte if // not a pointer and empty: - buf.Truncate(lBeforeKey) + buf.Set(buf.Bytes()[:lBeforeKey]) } return nil } diff --git a/tm2/pkg/amino/codec.go b/tm2/pkg/amino/codec.go index 3fa7634e3ad..a441128458a 100644 --- a/tm2/pkg/amino/codec.go +++ b/tm2/pkg/amino/codec.go @@ -1,7 +1,6 @@ package amino import ( - "bytes" "fmt" "io" "reflect" @@ -113,25 +112,27 @@ func (info *TypeInfo) String() string { // before it's fully populated. return "" } - buf := new(bytes.Buffer) - buf.Write([]byte("TypeInfo{")) - buf.Write([]byte(fmt.Sprintf("Type:%v,", info.Type))) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + + buf.WriteString("TypeInfo{") + fmt.Fprintf(buf, "Type:%v,", info.Type) if info.ConcreteInfo.Registered { - buf.Write([]byte("Registered:true,")) - buf.Write([]byte(fmt.Sprintf("PointerPreferred:%v,", info.PointerPreferred))) - buf.Write([]byte(fmt.Sprintf("TypeURL:\"%v\",", info.TypeURL))) + buf.WriteString("Registered:true,") + fmt.Fprintf(buf, "PointerPreferred:%v,", info.PointerPreferred) + fmt.Fprintf(buf, "TypeURL:\"%v\",", info.TypeURL) } else { - buf.Write([]byte("Registered:false,")) + buf.WriteString("Registered:false,") } if info.ReprType == info { - buf.Write([]byte(fmt.Sprintf("ReprType:,"))) + buf.WriteString("ReprType:,") } else { - buf.Write([]byte(fmt.Sprintf("ReprType:\"%v\",", info.ReprType))) + fmt.Fprintf(buf, "ReprType:\"%v\",", info.ReprType) } if info.Type.Kind() == reflect.Struct { - buf.Write([]byte(fmt.Sprintf("Fields:%v,", info.Fields))) + fmt.Fprintf(buf, "Fields:%v,", info.Fields) } - buf.Write([]byte("}")) + buf.WriteByte('}') return buf.String() } diff --git a/tm2/pkg/amino/codec_test.go b/tm2/pkg/amino/codec_test.go index 9368d4ef40e..89ced32b064 100644 --- a/tm2/pkg/amino/codec_test.go +++ b/tm2/pkg/amino/codec_test.go @@ -3,6 +3,7 @@ package amino_test import ( "bytes" "encoding/binary" + "reflect" "strings" "testing" "time" @@ -236,4 +237,69 @@ func TestCodecSeal(t *testing.T) { assert.Panics(t, func() { cdc.RegisterPackage(tests.Package) }) } -// XXX Test registering duplicate names or concrete types not in a package. +func TestDupTypesMustPanic(t *testing.T) { + // duplicate types must panic + t.Parallel() + + pkg := amino.NewPackage( + reflect.TypeOf(SimpleStruct{}).PkgPath(), + "amino_test", + amino.GetCallersDirname(), + ) + assert.PanicsWithError( + t, + "type amino_test.SimpleStruct already registered with package", + func() { + pkg.WithTypes( + SimpleStruct{}, + SimpleStruct{}, + ) + }) +} + +func TestTypesOutsidePackageMustPanic(t *testing.T) { + // adding concrete types from within a different package must panic + // (use dependency instead) + t.Parallel() + + makepkg := func() *amino.Package { + return amino.NewPackage( + reflect.TypeOf(tests.EmptyStruct{}).PkgPath(), + "amino_test", + amino.GetCallersDirname(), + ) + } + + makepkg().WithTypes(tests.PrimitivesStruct{}) // from same package ✓ + + assert.Panics(t, func() { + makepkg().WithTypes( + SimpleStruct{}, // from another package ✗ + ) + }) +} + +func TestDupNamesMustPanic(t *testing.T) { + // adding types with the same names must panic + t.Parallel() + + makepkg := func() *amino.Package { + return amino.NewPackage( + reflect.TypeOf(tests.EmptyStruct{}).PkgPath(), + "amino_test", + amino.GetCallersDirname(), + ) + } + makepkg().WithTypes( + tests.EmptyStruct{}, "A", + tests.PrimitivesStruct{}, "B", + tests.ShortArraysStruct{}, "C", + ) + assert.Panics(t, func() { + makepkg().WithTypes( + tests.EmptyStruct{}, "A", + tests.PrimitivesStruct{}, "B", + tests.ShortArraysStruct{}, "A", // Same name! + ) + }) +} diff --git a/tm2/pkg/amino/json_decode.go b/tm2/pkg/amino/json_decode.go index c213f4b155a..735ac6566a0 100644 --- a/tm2/pkg/amino/json_decode.go +++ b/tm2/pkg/amino/json_decode.go @@ -315,14 +315,21 @@ func (cdc *Codec) decodeReflectJSONSlice(bz []byte, info *TypeInfo, rv reflect.V return } - // Special case when length is 0. - // NOTE: We prefer nil slices. - length := len(rawSlice) - if length == 0 { + // Special case when rawSlice is nil. + // This happens when the JSON was 'null'. + if rawSlice == nil { rv.Set(info.ZeroValue) - return } + length := len(rawSlice) + // NOTE: While we prefer nil slices for binary decoding, + // we prefer empty slices for json "[]". + // This is also how json.Unmarshal() behaves. + // if length == 0 { + // rv.Set(info.ZeroValue) + // return + // } + // Read into a new slice. esrt := reflect.SliceOf(ert) // TODO could be optimized. srv := reflect.MakeSlice(esrt, length, length) diff --git a/tm2/pkg/amino/json_encode.go b/tm2/pkg/amino/json_encode.go index 113c3486565..99e1b445917 100644 --- a/tm2/pkg/amino/json_encode.go +++ b/tm2/pkg/amino/json_encode.go @@ -1,7 +1,6 @@ package amino import ( - "bytes" "encoding/json" "fmt" "io" @@ -156,7 +155,9 @@ func (cdc *Codec) encodeReflectJSONInterface(w io.Writer, iinfo *TypeInfo, rv re } // Write Value to buffer - buf := new(bytes.Buffer) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + cdc.encodeReflectJSON(buf, cinfo, crv, fopts) value := buf.Bytes() if len(value) == 0 { diff --git a/tm2/pkg/amino/pkg/pkg.go b/tm2/pkg/amino/pkg/pkg.go index b6ffab9748e..7a31256f184 100644 --- a/tm2/pkg/amino/pkg/pkg.go +++ b/tm2/pkg/amino/pkg/pkg.go @@ -118,11 +118,24 @@ func (pkg *Package) WithGoPkgName(name string) *Package { return pkg } +// Package dependencies need to be declared (for now). +// If a package has no dependency, it is conventional to +// use `.WithDependencies()` with no arguments. func (pkg *Package) WithDependencies(deps ...*Package) *Package { pkg.Dependencies = append(pkg.Dependencies, deps...) return pkg } +// WithType specifies which types are encoded and decoded by the package. +// You must provide a list of instantiated objects in the arguments. +// Each type declaration may be optionally followed by a string which is then +// used as its name. +// +// pkg.WithTypes( +// StructA{}, +// &StructB{}, // If pointer receivers are preferred when decoding to interfaces. +// NoInputsError{}, "NoInputsError", // Named +// ) func (pkg *Package) WithTypes(objs ...interface{}) *Package { var lastType *Type = nil for _, obj := range objs { @@ -183,6 +196,17 @@ func (pkg *Package) WithTypes(objs ...interface{}) *Package { } pkg.Types = append(pkg.Types, lastType) } + seen := make(map[string]struct{}) + for _, tp := range pkg.Types { + // tp.Name is "" for cases like nativePkg, containing go native types. + if tp.Name == "" { + continue + } + if _, ok := seen[tp.Name]; ok { + panic("duplicate type name " + tp.Name) + } + seen[tp.Name] = struct{}{} + } return pkg } diff --git a/tm2/pkg/amino/wellknown.go b/tm2/pkg/amino/wellknown.go index 7720c2894d9..53feb77be7e 100644 --- a/tm2/pkg/amino/wellknown.go +++ b/tm2/pkg/amino/wellknown.go @@ -3,7 +3,6 @@ package amino // NOTE: We must not depend on protubuf libraries for serialization. import ( - "bytes" "fmt" "io" "reflect" @@ -342,7 +341,9 @@ func encodeReflectBinaryWellKnown(w io.Writer, info *TypeInfo, rv reflect.Value, } // Maybe recurse with length-prefixing. if !bare { - buf := bytes.NewBuffer(nil) + buf := poolBytesBuffer.Get() + defer poolBytesBuffer.Put(buf) + ok, err = encodeReflectBinaryWellKnown(buf, info, rv, fopts, true) if err != nil { return false, err @@ -424,7 +425,7 @@ func EncodeJSONTimeValue(w io.Writer, s int64, ns int32) (err error) { x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, ".000") - _, err = w.Write([]byte(fmt.Sprintf(`"%vZ"`, x))) + _, err = fmt.Fprintf(w, `"%vZ"`, x) return err } @@ -455,7 +456,7 @@ func EncodeJSONDurationValue(w io.Writer, s int64, ns int32) (err error) { x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, ".000") - _, err = w.Write([]byte(fmt.Sprintf(`"%vs"`, x))) + _, err = fmt.Fprintf(w, `"%vs"`, x) return err } diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index c1afb2996fa..7f16d6780c7 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -12,8 +12,6 @@ import ( "sync" "time" - goErrors "errors" - "github.com/gnolang/gno/tm2/pkg/bft/appconn" "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" "github.com/gnolang/gno/tm2/pkg/p2p/conn" @@ -604,12 +602,10 @@ func (n *Node) OnStart() error { } // Start the transport. - lAddr := n.config.P2P.ExternalAddress - if lAddr == "" { - lAddr = n.config.P2P.ListenAddress - } + // The listen address for the transport needs to be an address within reach of the machine NIC + listenAddress := p2pTypes.NetAddressString(n.nodeKey.ID(), n.config.P2P.ListenAddress) - addr, err := p2pTypes.NewNetAddressFromString(p2pTypes.NetAddressString(n.nodeKey.ID(), lAddr)) + addr, err := p2pTypes.NewNetAddressFromString(listenAddress) if err != nil { return fmt.Errorf("unable to parse network address, %w", err) } @@ -713,7 +709,17 @@ func (n *Node) configureRPC() { rpccore.SetConfig(*n.config.RPC) } -func (n *Node) startRPC() ([]net.Listener, error) { +func (n *Node) startRPC() (listeners []net.Listener, err error) { + defer func() { + if err != nil { + // Close all the created listeners on any error, instead of + // leaking them: https://github.com/gnolang/gno/issues/3639 + for _, ln := range listeners { + ln.Close() + } + } + }() + listenAddrs := splitAndTrimEmpty(n.config.RPC.ListenAddress, ",", " ") config := rpcserver.DefaultConfig() @@ -729,8 +735,8 @@ func (n *Node) startRPC() ([]net.Listener, error) { // we may expose the rpc over both a unix and tcp socket var rebuildAddresses bool - listeners := make([]net.Listener, len(listenAddrs)) - for i, listenAddr := range listenAddrs { + listeners = make([]net.Listener, 0, len(listenAddrs)) + for _, listenAddr := range listenAddrs { mux := http.NewServeMux() rpcLogger := n.Logger.With("module", "rpc-server") wmLogger := rpcLogger.With("protocol", "websocket") @@ -782,7 +788,7 @@ func (n *Node) startRPC() ([]net.Listener, error) { ) } - listeners[i] = listener + listeners = append(listeners, listener) } if rebuildAddresses { n.config.RPC.ListenAddress = joinListenerAddresses(listeners) @@ -893,7 +899,7 @@ func makeNodeInfo( nodeInfo := p2pTypes.NodeInfo{ VersionSet: vset, - PeerID: nodeKey.ID(), + NetAddress: nil, // The shared address depends on the configuration Network: genDoc.ChainID, Version: version.Version, Channels: []byte{ @@ -908,13 +914,44 @@ func makeNodeInfo( }, } + // Make sure the discovery channel is shared with peers + // in case peer discovery is enabled if config.P2P.PeerExchange { nodeInfo.Channels = append(nodeInfo.Channels, discovery.Channel) } + // Grab the supplied listen address. + // This address needs to be valid, but it can be unspecified. + // If the listen address is unspecified (port / IP unbound), + // then this address cannot be used by peers for dialing + addr, err := p2pTypes.NewNetAddressFromString( + p2pTypes.NetAddressString(nodeKey.ID(), config.P2P.ListenAddress), + ) + if err != nil { + return p2pTypes.NodeInfo{}, fmt.Errorf("unable to parse network address, %w", err) + } + + // Use the transport listen address as the advertised address + nodeInfo.NetAddress = addr + + // Prepare the advertised dial address (if any) + // for the node, which other peers can use to dial + if config.P2P.ExternalAddress != "" { + addr, err = p2pTypes.NewNetAddressFromString( + p2pTypes.NetAddressString( + nodeKey.ID(), + config.P2P.ExternalAddress, + ), + ) + if err != nil { + return p2pTypes.NodeInfo{}, fmt.Errorf("invalid p2p external address: %w", err) + } + + nodeInfo.NetAddress = addr + } + // Validate the node info - err := nodeInfo.Validate() - if err != nil && !goErrors.Is(err, p2pTypes.ErrUnspecifiedIP) { + if err := nodeInfo.Validate(); err != nil { return p2pTypes.NodeInfo{}, fmt.Errorf("unable to validate node info, %w", err) } diff --git a/tm2/pkg/bft/privval/utils.go b/tm2/pkg/bft/privval/utils.go index c759d9dde9d..a98a36ef04a 100644 --- a/tm2/pkg/bft/privval/utils.go +++ b/tm2/pkg/bft/privval/utils.go @@ -25,7 +25,7 @@ func IsConnTimeout(err error) bool { } // NewSignerListener creates a new SignerListenerEndpoint using the corresponding listen address -func NewSignerListener(listenAddr string, logger *slog.Logger) (*SignerListenerEndpoint, error) { +func NewSignerListener(listenAddr string, logger *slog.Logger) (_ *SignerListenerEndpoint, rerr error) { var listener net.Listener protocol, address := osm.ProtocolAndAddress(listenAddr) @@ -33,6 +33,13 @@ func NewSignerListener(listenAddr string, logger *slog.Logger) (*SignerListenerE if err != nil { return nil, err } + + defer func() { + if rerr != nil { + ln.Close() + } + }() + switch protocol { case "unix": listener = NewUnixListener(ln) diff --git a/tm2/pkg/bft/rpc/lib/server/handlers.go b/tm2/pkg/bft/rpc/lib/server/handlers.go index 9e10596a975..fedbdff9649 100644 --- a/tm2/pkg/bft/rpc/lib/server/handlers.go +++ b/tm2/pkg/bft/rpc/lib/server/handlers.go @@ -10,9 +10,9 @@ import ( "log/slog" "net/http" "reflect" - "regexp" "runtime/debug" "sort" + "strconv" "strings" "time" @@ -140,61 +140,84 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger *slog.Logger) http.H return } - // first try to unmarshal the incoming request as an array of RPC requests - var ( - requests types.RPCRequests - responses types.RPCResponses - ) - if err := json.Unmarshal(b, &requests); err != nil { - // next, try to unmarshal as a single request - var request types.RPCRequest - if err := json.Unmarshal(b, &request); err != nil { - WriteRPCResponseHTTP(w, types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "error unmarshalling request"))) + // --- Branch 1: Attempt to Unmarshal as a Batch (Slice) of Requests --- + var requests types.RPCRequests + if err := json.Unmarshal(b, &requests); err == nil { + var responses types.RPCResponses + for _, req := range requests { + if resp := processRequest(r, req, funcMap, logger); resp != nil { + responses = append(responses, *resp) + } + } + + if len(responses) > 0 { + WriteRPCResponseArrayHTTP(w, responses) return } - requests = []types.RPCRequest{request} } - for _, request := range requests { - request := request - // A Notification is a Request object without an "id" member. - // The Server MUST NOT reply to a Notification, including those that are within a batch request. - if request.ID == types.JSONRPCStringID("") { - logger.Debug("HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)") - continue - } - if len(r.URL.Path) > 1 { - responses = append(responses, types.RPCInvalidRequestError(request.ID, errors.New("path %s is invalid", r.URL.Path))) - continue - } - rpcFunc, ok := funcMap[request.Method] - if !ok || rpcFunc.ws { - responses = append(responses, types.RPCMethodNotFoundError(request.ID)) - continue - } - ctx := &types.Context{JSONReq: &request, HTTPReq: r} - args := []reflect.Value{reflect.ValueOf(ctx)} - if len(request.Params) > 0 { - fnArgs, err := jsonParamsToArgs(rpcFunc, request.Params) - if err != nil { - responses = append(responses, types.RPCInvalidParamsError(request.ID, errors.Wrap(err, "error converting json params to arguments"))) - continue - } - args = append(args, fnArgs...) - } - returns := rpcFunc.f.Call(args) - logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns) - result, err := unreflectResult(returns) - if err != nil { - responses = append(responses, types.RPCInternalError(request.ID, err)) - continue + // --- Branch 2: Attempt to Unmarshal as a Single Request --- + var request types.RPCRequest + if err := json.Unmarshal(b, &request); err == nil { + if resp := processRequest(r, request, funcMap, logger); resp != nil { + WriteRPCResponseHTTP(w, *resp) + return } - responses = append(responses, types.NewRPCSuccessResponse(request.ID, result)) + } else { + WriteRPCResponseHTTP(w, types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "error unmarshalling request"))) + return } - if len(responses) > 0 { - WriteRPCResponseArrayHTTP(w, responses) + } +} + +// processRequest checks and processes a single JSON-RPC request. +// If the request should produce a response, it returns a pointer to that response. +// Otherwise (e.g. if the request is a notification or fails validation), it returns nil. +func processRequest(r *http.Request, req types.RPCRequest, funcMap map[string]*RPCFunc, logger *slog.Logger) *types.RPCResponse { + // Skip notifications (an empty ID indicates no response should be sent) + if req.ID == types.JSONRPCStringID("") { + logger.Debug("Skipping notification (empty ID)") + return nil + } + + // Check that the URL path is valid (assume only "/" is acceptable) + if len(r.URL.Path) > 1 { + resp := types.RPCInvalidRequestError(req.ID, fmt.Errorf("invalid path: %s", r.URL.Path)) + return &resp + } + + // Look up the requested method in the function map. + rpcFunc, ok := funcMap[req.Method] + if !ok || rpcFunc.ws { + resp := types.RPCMethodNotFoundError(req.ID) + return &resp + } + + ctx := &types.Context{JSONReq: &req, HTTPReq: r} + args := []reflect.Value{reflect.ValueOf(ctx)} + if len(req.Params) > 0 { + fnArgs, err := jsonParamsToArgs(rpcFunc, req.Params) + if err != nil { + resp := types.RPCInvalidParamsError(req.ID, errors.Wrap(err, "error converting json params to arguments")) + return &resp } + args = append(args, fnArgs...) + } + + // Call the RPC function using reflection. + returns := rpcFunc.f.Call(args) + logger.Info("HTTPJSONRPC", "method", req.Method, "args", args, "returns", returns) + + // Convert the reflection return values into a result value for JSON serialization. + result, err := unreflectResult(returns) + if err != nil { + resp := types.RPCInternalError(req.ID, err) + return &resp } + + // Build and return a successful response. + resp := types.NewRPCSuccessResponse(req.ID, result) + return &resp } func handleInvalidJSONRPCPaths(next http.HandlerFunc) http.HandlerFunc { @@ -335,127 +358,57 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger *slog.Logger) http.HandlerFunc { // Convert an http query to a list of properly typed values. // To be properly decoded the arg must be a concrete type from tendermint (if its an interface). func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) { - // skip types.Context const argsOffset = 1 - values := make([]reflect.Value, len(rpcFunc.argNames)) - - for i, name := range rpcFunc.argNames { - argType := rpcFunc.args[i+argsOffset] - - values[i] = reflect.Zero(argType) // set default for that type - - arg := GetParam(r, name) - // log.Notice("param to arg", "argType", argType, "name", name, "arg", arg) - + paramsMap := make(map[string]json.RawMessage) + for _, argName := range rpcFunc.argNames { + arg := GetParam(r, argName) if arg == "" { + // Empty param continue } - v, err, ok := nonJSONStringToArg(argType, arg) - if err != nil { - return nil, err - } - if ok { - values[i] = v - continue - } + // Handle hex string + if strings.HasPrefix(arg, "0x") { + decoded, err := hex.DecodeString(arg[2:]) + if err != nil { + return nil, fmt.Errorf("unable to decode hex string: %w", err) + } - values[i], err = jsonStringToArg(argType, arg) - if err != nil { - return nil, err - } - } + data, err := amino.MarshalJSON(decoded) + if err != nil { + return nil, fmt.Errorf("unable to marshal argument to JSON: %w", err) + } - return values, nil -} + paramsMap[argName] = data -func jsonStringToArg(rt reflect.Type, arg string) (reflect.Value, error) { - rv := reflect.New(rt) - err := amino.UnmarshalJSON([]byte(arg), rv.Interface()) - if err != nil { - return rv, err - } - rv = rv.Elem() - return rv, nil -} - -func nonJSONStringToArg(rt reflect.Type, arg string) (reflect.Value, error, bool) { - if rt.Kind() == reflect.Ptr { - rv_, err, ok := nonJSONStringToArg(rt.Elem(), arg) - switch { - case err != nil: - return reflect.Value{}, err, false - case ok: - rv := reflect.New(rt.Elem()) - rv.Elem().Set(rv_) - return rv, nil, true - default: - return reflect.Value{}, nil, false + continue } - } else { - return _nonJSONStringToArg(rt, arg) - } -} - -var reInt = regexp.MustCompile(`^-?[0-9]+$`) - -// NOTE: rt.Kind() isn't a pointer. -func _nonJSONStringToArg(rt reflect.Type, arg string) (reflect.Value, error, bool) { - isIntString := reInt.Match([]byte(arg)) - isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`) - isHexString := strings.HasPrefix(strings.ToLower(arg), "0x") - var expectingString, expectingByteSlice, expectingInt bool - switch rt.Kind() { - case reflect.Int, reflect.Uint, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64: - expectingInt = true - case reflect.String: - expectingString = true - case reflect.Slice: - expectingByteSlice = rt.Elem().Kind() == reflect.Uint8 - } - - if isIntString && expectingInt { - qarg := `"` + arg + `"` - // jsonStringToArg - rv, err := jsonStringToArg(rt, qarg) - if err != nil { - return rv, err, false + // Handle integer string by adding quotes to ensure it is treated as a JSON string. + // This is required by Amino JSON to unmarshal values into integers. + if _, err := strconv.Atoi(arg); err == nil { + // arg is a number, wrap it + arg = "\"" + arg + "\"" } - return rv, nil, true - } + // Handle invalid JSON: ensure it's wrapped as a JSON-encoded string + if !json.Valid([]byte(arg)) { + data, err := amino.MarshalJSON(arg) + if err != nil { + return nil, fmt.Errorf("unable to marshal argument to JSON: %w", err) + } - if isHexString { - if !expectingString && !expectingByteSlice { - err := errors.New("got a hex string arg, but expected '%s'", - rt.Kind().String()) - return reflect.ValueOf(nil), err, false - } + paramsMap[argName] = data - var value []byte - value, err := hex.DecodeString(arg[2:]) - if err != nil { - return reflect.ValueOf(nil), err, false - } - if rt.Kind() == reflect.String { - return reflect.ValueOf(string(value)), nil, true + continue } - return reflect.ValueOf(value), nil, true - } - if isQuotedString && expectingByteSlice { - v := reflect.New(reflect.TypeOf("")) - err := amino.UnmarshalJSON([]byte(arg), v.Interface()) - if err != nil { - return reflect.ValueOf(nil), err, false - } - v = v.Elem() - return reflect.ValueOf([]byte(v.String())), nil, true + // Default: treat the argument as a JSON raw message + paramsMap[argName] = json.RawMessage([]byte(arg)) } - return reflect.ValueOf(nil), nil, false + return mapParamsToArgs(rpcFunc, paramsMap, argsOffset) } // rpc.http diff --git a/tm2/pkg/bft/rpc/lib/server/handlers_test.go b/tm2/pkg/bft/rpc/lib/server/handlers_test.go index f6572be7e0a..dde2cf1e327 100644 --- a/tm2/pkg/bft/rpc/lib/server/handlers_test.go +++ b/tm2/pkg/bft/rpc/lib/server/handlers_test.go @@ -171,6 +171,12 @@ func TestRPCNotificationInBatch(t *testing.T) { ]`, 1, }, + { + `[ + {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]} + ]`, + 1, + }, { `[ {"jsonrpc": "2.0","id": ""}, @@ -198,21 +204,8 @@ func TestRPCNotificationInBatch(t *testing.T) { // try to unmarshal an array first err = json.Unmarshal(blob, &responses) if err != nil { - // if we were actually expecting an array, but got an error - if tt.expectCount > 1 { - t.Errorf("#%d: expected an array, couldn't unmarshal it\nblob: %s", i, blob) - continue - } else { - // we were expecting an error here, so let's unmarshal a single response - var response types.RPCResponse - err = json.Unmarshal(blob, &response) - if err != nil { - t.Errorf("#%d: expected successful parsing of an RPCResponse\nblob: %s", i, blob) - continue - } - // have a single-element result - responses = types.RPCResponses{response} - } + t.Errorf("#%d: expected an array, couldn't unmarshal it\nblob: %s", i, blob) + continue } if tt.expectCount != len(responses) { t.Errorf("#%d: expected %d response(s), but got %d\nblob: %s", i, tt.expectCount, len(responses), blob) diff --git a/tm2/pkg/bft/rpc/lib/server/http_server.go b/tm2/pkg/bft/rpc/lib/server/http_server.go index a4e535160b5..a5cec3d5c81 100644 --- a/tm2/pkg/bft/rpc/lib/server/http_server.go +++ b/tm2/pkg/bft/rpc/lib/server/http_server.go @@ -119,18 +119,14 @@ func WriteRPCResponseHTTP(w http.ResponseWriter, res types.RPCResponse) { // can write arrays of responses for batched request/response interactions via // the JSON RPC. func WriteRPCResponseArrayHTTP(w http.ResponseWriter, res types.RPCResponses) { - if len(res) == 1 { - WriteRPCResponseHTTP(w, res[0]) - } else { - jsonBytes, err := json.MarshalIndent(res, "", " ") - if err != nil { - panic(err) - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - if _, err := w.Write(jsonBytes); err != nil { - panic(err) - } + jsonBytes, err := json.MarshalIndent(res, "", " ") + if err != nil { + panic(err) + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + if _, err := w.Write(jsonBytes); err != nil { + panic(err) } } diff --git a/tm2/pkg/bft/rpc/lib/server/parse_test.go b/tm2/pkg/bft/rpc/lib/server/parse_test.go index aece3f20f3a..8ebfbd17c72 100644 --- a/tm2/pkg/bft/rpc/lib/server/parse_test.go +++ b/tm2/pkg/bft/rpc/lib/server/parse_test.go @@ -1,13 +1,18 @@ package rpcserver import ( + "bytes" + "encoding/base64" + "encoding/hex" "encoding/json" "fmt" "net/http" + "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" ) @@ -175,45 +180,128 @@ func TestParseJSONRPC(t *testing.T) { } } -func TestParseURI(t *testing.T) { +func TestParseURINonJSON(t *testing.T) { t.Parallel() - demo := func(ctx *types.Context, height int, name string) {} - call := NewRPCFunc(demo, "height,name") + // Define a demo RPC function + demo := func(ctx *types.Context, height int, name string, hash []byte) {} + call := NewRPCFunc(demo, "height,name,hash") - cases := []struct { + // Helper function to decode input base64 string to []byte + decodeBase64 := func(input string) []byte { + decoded, _ := base64.StdEncoding.DecodeString(input) + return decoded + } + + // Helper function to decode input hex string to []byte + decodeHex := func(input string) []byte { + decoded, _ := hex.DecodeString(input[2:]) + return decoded + } + + // Test cases for non-JSON encoded parameters + nonJSONCases := []struct { raw []string height int64 name string + hash []byte fail bool }{ - // can parse numbers unquoted and strings quoted - {[]string{"7", `"flew"`}, 7, "flew", false}, - {[]string{"22", `"john"`}, 22, "john", false}, - {[]string{"-10", `"bob"`}, -10, "bob", false}, + // can parse numbers and strings, quoted and unquoted + {[]string{"7", `"flew"`, "MzMz"}, 7, "flew", decodeBase64("MzMz"), false}, + {[]string{"22", `john`, "MzM="}, 22, "john", decodeBase64("MzM="), false}, + {[]string{"-10", `"bob"`, "Z25v"}, -10, "bob", decodeBase64("Z25v"), false}, // can parse numbers quoted, too - {[]string{`"7"`, `"flew"`}, 7, "flew", false}, - {[]string{`"-10"`, `"bob"`}, -10, "bob", false}, - // cant parse strings uquoted - {[]string{`"-10"`, `bob`}, -10, "bob", true}, + {[]string{`"7"`, `"flew"`, "0x486173682076616c7565"}, 7, "flew", decodeHex("0x486173682076616c7565"), false}, // Testing hex encoded data + {[]string{`"-10"`, `"bob"`, "0x6578616d706c65"}, -10, "bob", decodeHex("0x6578616d706c65"), false}, // Testing hex encoded data + // []byte must be base64 + {[]string{`"-10"`, `bob`, "invalid_encoded_data"}, -10, "bob", []byte("invalid_encoded_data"), true}, // Invalid encoded data format } - for idx, tc := range cases { - i := strconv.Itoa(idx) - // data := []byte(tc.raw) - url := fmt.Sprintf( - "test.com/method?height=%v&name=%v", - tc.raw[0], tc.raw[1]) - req, err := http.NewRequest("GET", url, nil) - assert.NoError(t, err) - vals, err := httpParamsToArgs(call, req) - if tc.fail { - assert.NotNil(t, err, i) - } else { + + // Iterate over test cases for non-JSON encoded parameters + for idx, tc := range nonJSONCases { + t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) { + t.Parallel() + + i := strconv.Itoa(idx) + url := fmt.Sprintf("test.com/method?height=%v&name=%v&hash=%v", tc.raw[0], tc.raw[1], url.QueryEscape(tc.raw[2])) + req, err := http.NewRequest("GET", url, nil) + + assert.NoError(t, err) + + // Invoke httpParamsToArgs to parse the request and convert to reflect.Values + vals, err := httpParamsToArgs(call, req) + + // Check for expected errors or successful parsing + if tc.fail { + assert.NotNil(t, err, i) + + return + } + assert.Nil(t, err, "%s: %+v", i, err) - if assert.Equal(t, 2, len(vals), i) { + // Assert the parsed values match the expected height, name, and data + + if assert.Equal(t, 3, len(vals), i) { assert.Equal(t, tc.height, vals[0].Int(), i) assert.Equal(t, tc.name, vals[1].String(), i) + assert.Equal(t, len(tc.hash), len(vals[2].Bytes()), i) + assert.True(t, bytes.Equal(tc.hash, vals[2].Bytes()), i) } - } + }) + } +} + +func TestParseURI_JSON(t *testing.T) { + t.Parallel() + + type Data struct { + Key string `json:"key"` + } + + // Define a demo RPC function + demo := func(ctx *types.Context, data Data) {} + call := NewRPCFunc(demo, "data") + + // Test cases for JSON encoded parameters + jsonCases := []struct { + raw string + data Data + fail bool + }{ + // Valid JSON encoded values + {`{"key": "value"}`, Data{Key: "value"}, false}, + {`{"id": 123}`, Data{}, false}, // Invalid field "id" (not in struct) + {`{"list": [1, 2, 3]}`, Data{}, false}, // Invalid field "list" (not in struct) + // Invalid JSON encoded values + {`"string_data"`, Data{}, true}, // Invalid JSON format (not an object) + {`12345`, Data{}, true}, // Invalid JSON format (not an object) + {`{"key": true}`, Data{}, true}, // Invalid field "key" type (expected string) + {`{"key": {"nested": "value"}}`, Data{}, true}, // Invalid field "key" type (nested object) + } + + // Iterate over test cases for JSON encoded parameters + for _, tc := range jsonCases { + t.Run(tc.raw, func(t *testing.T) { + t.Parallel() + url := fmt.Sprintf("test.com/method?data=%v", url.PathEscape(tc.raw)) + req, err := http.NewRequest("GET", url, nil) + assert.NoError(t, err) + + // Invoke httpParamsToArgs to parse the request and convert to reflect.Values + vals, err := httpParamsToArgs(call, req) + + // Check for expected errors or successful parsing + if tc.fail { + assert.NotNil(t, err) + return + } + + assert.Nil(t, err, " %+v", err) + + // Assert the parsed values match the expected data + require.Len(t, vals, 1) + assert.Equal(t, tc.data, vals[0].Interface()) + }) } } diff --git a/tm2/pkg/bft/types/params.go b/tm2/pkg/bft/types/params.go index c2e8f304698..323e12c25cd 100644 --- a/tm2/pkg/bft/types/params.go +++ b/tm2/pkg/bft/types/params.go @@ -24,7 +24,7 @@ const ( MaxBlockDataBytes int64 = 2000000 // 2MB // MaxBlockMaxGas is the max gas limit for the block - MaxBlockMaxGas int64 = 100000000 // 100M gas + MaxBlockMaxGas int64 = 3000000000 // 3B gas // BlockTimeIotaMS is the block time iota (in ms) BlockTimeIotaMS int64 = 100 // ms diff --git a/tm2/pkg/cmap/cmap_test.go b/tm2/pkg/cmap/cmap_test.go index d9051ea18d6..ebeb601633d 100644 --- a/tm2/pkg/cmap/cmap_test.go +++ b/tm2/pkg/cmap/cmap_test.go @@ -2,7 +2,9 @@ package cmap import ( "fmt" + "runtime" "strings" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -56,6 +58,61 @@ func TestContains(t *testing.T) { assert.Nil(t, cmap.Get("key2")) } +var sink any = nil + +func BenchmarkCMapConcurrentInsertsDeletesHas(b *testing.B) { + cm := NewCMap() + keys := make([]string, 100000) + for i := range keys { + keys[i] = fmt.Sprintf("key%d", i) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + var wg sync.WaitGroup + semaCh := make(chan bool) + nCPU := runtime.NumCPU() + for j := 0; j < nCPU; j++ { + wg.Add(1) + go func() { + defer wg.Done() + + // Make sure that all the goroutines run at the + // exact same time for true concurrent tests. + <-semaCh + + for i, key := range keys { + if (j+i)%2 == 0 { + cm.Has(key) + } else { + cm.Set(key, j) + } + _ = cm.Size() + if (i+1)%3 == 0 { + cm.Delete(key) + } + + if (i+1)%327 == 0 { + cm.Clear() + } + _ = cm.Size() + _ = cm.Keys() + } + _ = cm.Values() + }() + } + close(semaCh) + wg.Wait() + + sink = semaCh + } + + if sink == nil { + b.Fatal("Benchmark did not run!") + } + sink = nil +} + func BenchmarkCMapHas(b *testing.B) { m := NewCMap() for i := 0; i < 1000; i++ { diff --git a/tm2/pkg/commands/command.go b/tm2/pkg/commands/command.go index aa717b62ad9..a7f80b69a70 100644 --- a/tm2/pkg/commands/command.go +++ b/tm2/pkg/commands/command.go @@ -5,6 +5,7 @@ import ( "errors" "flag" "fmt" + "io" "os" "strings" "text/tabwriter" @@ -31,26 +32,28 @@ func HelpExec(_ context.Context, _ []string) error { // Metadata contains basic help // information about a command type Metadata struct { - Name string - ShortUsage string - ShortHelp string - LongHelp string - Options []ff.Option + Name string + ShortUsage string + ShortHelp string + LongHelp string + Options []ff.Option + NoParentFlags bool } // Command is a simple wrapper for gnoland commands. type Command struct { - name string - shortUsage string - shortHelp string - longHelp string - options []ff.Option - cfg Config - flagSet *flag.FlagSet - subcommands []*Command - exec ExecMethod - selected *Command - args []string + name string + shortUsage string + shortHelp string + longHelp string + options []ff.Option + cfg Config + flagSet *flag.FlagSet + subcommands []*Command + exec ExecMethod + selected *Command + args []string + noParentFlags bool } func NewCommand( @@ -59,14 +62,15 @@ func NewCommand( exec ExecMethod, ) *Command { command := &Command{ - name: meta.Name, - shortUsage: meta.ShortUsage, - shortHelp: meta.ShortHelp, - longHelp: meta.LongHelp, - options: meta.Options, - flagSet: flag.NewFlagSet(meta.Name, flag.ContinueOnError), - exec: exec, - cfg: config, + name: meta.Name, + shortUsage: meta.ShortUsage, + shortHelp: meta.ShortHelp, + longHelp: meta.LongHelp, + options: meta.Options, + noParentFlags: meta.NoParentFlags, + flagSet: flag.NewFlagSet(meta.Name, flag.ContinueOnError), + exec: exec, + cfg: config, } if config != nil { @@ -77,11 +81,17 @@ func NewCommand( return command } +// SetOutput sets the destination for usage and error messages. +// If output is nil, [os.Stderr] is used. +func (c *Command) SetOutput(output io.Writer) { + c.flagSet.SetOutput(output) +} + // AddSubCommands adds a variable number of subcommands // and registers common flags using the flagset func (c *Command) AddSubCommands(cmds ...*Command) { for _, cmd := range cmds { - if c.cfg != nil { + if c.cfg != nil && !cmd.noParentFlags { // Register the parent flagset with the child. // The syntax is not intuitive, but the flagset being // modified is the subcommand's, using the flags defined diff --git a/tm2/pkg/crypto/keys/client/maketx.go b/tm2/pkg/crypto/keys/client/maketx.go index 0801fcfe227..9ba24425a74 100644 --- a/tm2/pkg/crypto/keys/client/maketx.go +++ b/tm2/pkg/crypto/keys/client/maketx.go @@ -212,6 +212,7 @@ func ExecSignAndBroadcast( } if bres.DeliverTx.IsErr() { io.Println("TX HASH: ", base64.StdEncoding.EncodeToString(bres.Hash)) + io.Println("INFO: ", bres.DeliverTx.Info) return errors.Wrapf(bres.DeliverTx.Error, "deliver transaction failed: log:%s", bres.DeliverTx.Log) } @@ -221,6 +222,7 @@ func ExecSignAndBroadcast( io.Println("GAS USED: ", bres.DeliverTx.GasUsed) io.Println("HEIGHT: ", bres.Height) io.Println("EVENTS: ", string(bres.DeliverTx.EncodeEvents())) + io.Println("INFO: ", bres.DeliverTx.Info) io.Println("TX HASH: ", base64.StdEncoding.EncodeToString(bres.Hash)) return nil diff --git a/tm2/pkg/crypto/mock/mock.go b/tm2/pkg/crypto/mock/mock.go index 9ea1c5d66dc..b3fe8c5e69f 100644 --- a/tm2/pkg/crypto/mock/mock.go +++ b/tm2/pkg/crypto/mock/mock.go @@ -42,7 +42,7 @@ func (privKey PrivKeyMock) Equals(other crypto.PrivKey) bool { func GenPrivKey() PrivKeyMock { randstr := random.RandStr(12) - return PrivKeyMock([]byte(randstr)) + return []byte(randstr) } // ------------------------------------- diff --git a/tm2/pkg/errors/errors.go b/tm2/pkg/errors/errors.go index 1b40c903c41..f465e9898dd 100644 --- a/tm2/pkg/errors/errors.go +++ b/tm2/pkg/errors/errors.go @@ -154,7 +154,7 @@ func (err *cmnError) doTrace(msg string, n int) Error { func (err *cmnError) Format(s fmt.State, verb rune) { switch { case verb == 'p': - s.Write([]byte(fmt.Sprintf("%p", &err))) + fmt.Fprintf(s, "%p", &err) case verb == 'v' && s.Flag('+'): s.Write([]byte("--= Error =--\n")) // Write data. diff --git a/tm2/pkg/iavl/tree_fuzz_test.go b/tm2/pkg/iavl/tree_fuzz_test.go index 08645414fbf..ba709cc9da2 100644 --- a/tm2/pkg/iavl/tree_fuzz_test.go +++ b/tm2/pkg/iavl/tree_fuzz_test.go @@ -1,8 +1,14 @@ package iavl import ( + "encoding/json" "fmt" + "io" + "io/fs" "math/rand" + "os" + "path/filepath" + "strings" "testing" "github.com/gnolang/gno/tm2/pkg/db/memdb" @@ -14,28 +20,36 @@ import ( // A program is a list of instructions. type program struct { - instructions []instruction + Instructions []instruction `json:"instructions"` } func (p *program) Execute(tree *MutableTree) (err error) { var errLine int defer func() { - if r := recover(); r != nil { - var str string - - for i, instr := range p.instructions { - prefix := " " - if i == errLine { - prefix = ">> " - } - str += prefix + instr.String() + "\n" + r := recover() + if r == nil { + return + } + + // These are simply input errors and shouldn't be reported as actual logical issues. + if containsAny(fmt.Sprint(r), "Unrecognized op:", "Attempt to store nil value at key") { + return + } + + var str string + + for i, instr := range p.Instructions { + prefix := " " + if i == errLine { + prefix = ">> " } - err = fmt.Errorf("Program panicked with: %s\n%s", r, str) + str += prefix + instr.String() + "\n" } + err = fmt.Errorf("Program panicked with: %s\n%s", r, str) }() - for i, instr := range p.instructions { + for i, instr := range p.Instructions { errLine = i instr.Execute(tree) } @@ -43,39 +57,39 @@ func (p *program) Execute(tree *MutableTree) (err error) { } func (p *program) addInstruction(i instruction) { - p.instructions = append(p.instructions, i) + p.Instructions = append(p.Instructions, i) } func (p *program) size() int { - return len(p.instructions) + return len(p.Instructions) } type instruction struct { - op string - k, v []byte - version int64 + Op string + K, V []byte + Version int64 } func (i instruction) Execute(tree *MutableTree) { - switch i.op { + switch i.Op { case "SET": - tree.Set(i.k, i.v) + tree.Set(i.K, i.V) case "REMOVE": - tree.Remove(i.k) + tree.Remove(i.K) case "SAVE": tree.SaveVersion() case "DELETE": - tree.DeleteVersion(i.version) + tree.DeleteVersion(i.Version) default: - panic("Unrecognized op: " + i.op) + panic("Unrecognized op: " + i.Op) } } func (i instruction) String() string { - if i.version > 0 { - return fmt.Sprintf("%-8s %-8s %-8s %-8d", i.op, i.k, i.v, i.version) + if i.Version > 0 { + return fmt.Sprintf("%-8s %-8s %-8s %-8d", i.Op, i.K, i.V, i.Version) } - return fmt.Sprintf("%-8s %-8s %-8s", i.op, i.k, i.v) + return fmt.Sprintf("%-8s %-8s %-8s", i.Op, i.K, i.V) } // Generate a random program of the given size. @@ -88,15 +102,15 @@ func genRandomProgram(size int) *program { switch rand.Int() % 7 { case 0, 1, 2: - p.addInstruction(instruction{op: "SET", k: k, v: v}) + p.addInstruction(instruction{Op: "SET", K: k, V: v}) case 3, 4: - p.addInstruction(instruction{op: "REMOVE", k: k}) + p.addInstruction(instruction{Op: "REMOVE", K: k}) case 5: - p.addInstruction(instruction{op: "SAVE", version: int64(nextVersion)}) + p.addInstruction(instruction{Op: "SAVE", Version: int64(nextVersion)}) nextVersion++ case 6: if rv := rand.Int() % nextVersion; rv < nextVersion && rv > 0 { - p.addInstruction(instruction{op: "DELETE", version: int64(rv)}) + p.addInstruction(instruction{Op: "DELETE", Version: int64(rv)}) } } } @@ -107,19 +121,174 @@ func genRandomProgram(size int) *program { func TestMutableTreeFuzz(t *testing.T) { t.Parallel() + runThenGenerateMutableTreeFuzzSeeds(t, false) +} + +var pathForMutableTreeProgramSeeds = filepath.Join("testdata", "corpora", "mutable_tree_programs") + +func runThenGenerateMutableTreeFuzzSeeds(tb testing.TB, writeSeedsToFileSystem bool) { + tb.Helper() + + if testing.Short() { + tb.Skip("Running in -short mode") + } + maxIterations := testFuzzIterations progsPerIteration := 100000 iterations := 0 + if writeSeedsToFileSystem { + if err := os.MkdirAll(pathForMutableTreeProgramSeeds, 0o755); err != nil { + tb.Fatal(err) + } + } + for size := 5; iterations < maxIterations; size++ { for i := 0; i < progsPerIteration/size; i++ { tree := NewMutableTree(memdb.NewMemDB(), 0) program := genRandomProgram(size) err := program.Execute(tree) if err != nil { - t.Fatalf("Error after %d iterations (size %d): %s\n%s", iterations, size, err.Error(), tree.String()) + tb.Fatalf("Error after %d iterations (size %d): %s\n%s", iterations, size, err.Error(), tree.String()) } iterations++ + + if !writeSeedsToFileSystem { + continue + } + + // Otherwise write them to the testdata/corpra directory. + programJSON, err := json.Marshal(program) + if err != nil { + tb.Fatal(err) + } + path := filepath.Join(pathForMutableTreeProgramSeeds, fmt.Sprintf("%d", i+1)) + if err := os.WriteFile(path, programJSON, 0o755); err != nil { + tb.Fatal(err) + } + } + } +} + +type treeRange struct { + Start []byte + End []byte + Forward bool +} + +var basicRecords = []struct { + key, value string +}{ + {"abc", "123"}, + {"low", "high"}, + {"fan", "456"}, + {"foo", "a"}, + {"foobaz", "c"}, + {"good", "bye"}, + {"foobang", "d"}, + {"foobar", "b"}, + {"food", "e"}, + {"foml", "f"}, +} + +// Allows hooking into Go's fuzzers and then for continuous fuzzing +// enriched with coverage guided mutations, instead of naive mutations. +func FuzzIterateRange(f *testing.F) { + if testing.Short() { + f.Skip("Skipping in -short mode") + } + + // 1. Add the seeds. + seeds := []*treeRange{ + {[]byte("foo"), []byte("goo"), true}, + {[]byte("aaa"), []byte("abb"), true}, + {nil, []byte("flap"), true}, + {[]byte("foob"), nil, true}, + {[]byte("very"), nil, true}, + {[]byte("very"), nil, false}, + {[]byte("fooba"), []byte("food"), true}, + {[]byte("fooba"), []byte("food"), false}, + {[]byte("g"), nil, false}, + } + for _, seed := range seeds { + blob, err := json.Marshal(seed) + if err != nil { + f.Fatal(err) + } + f.Add(blob) + } + + db := memdb.NewMemDB() + tree := NewMutableTree(db, 0) + for _, br := range basicRecords { + tree.Set([]byte(br.key), []byte(br.value)) + } + + var trav traverser + + // 2. Run the fuzzer. + f.Fuzz(func(t *testing.T, rangeJSON []byte) { + tr := new(treeRange) + if err := json.Unmarshal(rangeJSON, tr); err != nil { + return + } + + tree.IterateRange(tr.Start, tr.End, tr.Forward, trav.view) + }) +} + +func containsAny(s string, anyOf ...string) bool { + for _, q := range anyOf { + if strings.Contains(s, q) { + return true + } + } + return false +} + +func FuzzMutableTreeInstructions(f *testing.F) { + if testing.Short() { + f.Skip("Skipping in -short mode") + } + + // 0. Generate then add the seeds. + runThenGenerateMutableTreeFuzzSeeds(f, true) + + // 1. Add the seeds. + dir := os.DirFS("testdata") + err := fs.WalkDir(dir, ".", func(path string, de fs.DirEntry, err error) error { + if de.IsDir() { + return err + } + + ff, err := dir.Open(path) + if err != nil { + return err + } + defer ff.Close() + + blob, err := io.ReadAll(ff) + if err != nil { + return err } + f.Add(blob) + return nil + }) + if err != nil { + f.Fatal(err) } + + // 2. Run the fuzzer. + f.Fuzz(func(t *testing.T, programJSON []byte) { + program := new(program) + if err := json.Unmarshal(programJSON, program); err != nil { + return + } + + tree := NewMutableTree(memdb.NewMemDB(), 0) + err := program.Execute(tree) + if err != nil { + t.Fatal(err) + } + }) } diff --git a/tm2/pkg/internal/p2p/p2p.go b/tm2/pkg/internal/p2p/p2p.go index 1e650e0cd25..0c8f1529b85 100644 --- a/tm2/pkg/internal/p2p/p2p.go +++ b/tm2/pkg/internal/p2p/p2p.go @@ -70,12 +70,12 @@ func MakeConnectedPeers( VersionSet: versionset.VersionSet{ versionset.VersionInfo{Name: "p2p", Version: "v0.0.0"}, }, - PeerID: key.ID(), - Network: "testing", - Software: "p2ptest", - Version: "v1.2.3-rc.0-deadbeef", - Channels: cfg.Channels, - Moniker: fmt.Sprintf("node-%d", index), + NetAddress: addr, + Network: "testing", + Software: "p2ptest", + Version: "v1.2.3-rc.0-deadbeef", + Channels: cfg.Channels, + Moniker: fmt.Sprintf("node-%d", index), Other: p2pTypes.NodeInfoOther{ TxIndex: "off", RPCAddress: fmt.Sprintf("127.0.0.1:%d", 0), @@ -231,7 +231,7 @@ func (mp *Peer) TrySend(_ byte, _ []byte) bool { return true } func (mp *Peer) Send(_ byte, _ []byte) bool { return true } func (mp *Peer) NodeInfo() p2pTypes.NodeInfo { return p2pTypes.NodeInfo{ - PeerID: mp.id, + NetAddress: mp.addr, } } func (mp *Peer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } diff --git a/tm2/pkg/os/tempfile.go b/tm2/pkg/os/tempfile.go index 277813051fd..46d97739b80 100644 --- a/tm2/pkg/os/tempfile.go +++ b/tm2/pkg/os/tempfile.go @@ -107,14 +107,17 @@ func WriteFileAtomic(filename string, data []byte, perm os.FileMode) (err error) } break } + + // Clean up in any case. + defer func() { + f.Close() + os.Remove(f.Name()) + }() + if i == atomicWriteFileMaxNumWriteAttempts { return fmt.Errorf("could not create atomic write file after %d attempts", i) } - // Clean up in any case. Defer stacking order is last-in-first-out. - defer os.Remove(f.Name()) - defer f.Close() - if n, err := f.Write(data); err != nil { return err } else if n < len(data) { diff --git a/tm2/pkg/overflow/overflow_impl.go b/tm2/pkg/overflow/overflow_impl.go index 0f057f65387..ab9f13c163d 100644 --- a/tm2/pkg/overflow/overflow_impl.go +++ b/tm2/pkg/overflow/overflow_impl.go @@ -84,10 +84,9 @@ func Quotient8(a, b int8) (int8, int8, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a%b, status + return c, a % b, status } - // Add16 performs + operation on two int16 operands, returning a result and status. func Add16(a, b int16) (int16, bool) { c := a + b @@ -170,10 +169,9 @@ func Quotient16(a, b int16) (int16, int16, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a%b, status + return c, a % b, status } - // Add32 performs + operation on two int32 operands, returning a result and status. func Add32(a, b int32) (int32, bool) { c := a + b @@ -256,10 +254,9 @@ func Quotient32(a, b int32) (int32, int32, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a%b, status + return c, a % b, status } - // Add64 performs + operation on two int64 operands, returning a result and status. func Add64(a, b int64) (int64, bool) { c := a + b @@ -342,6 +339,5 @@ func Quotient64(a, b int64) (int64, int64, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a%b, status + return c, a % b, status } - diff --git a/tm2/pkg/overflow/overflow_template.sh b/tm2/pkg/overflow/overflow_template.sh index 0cc3c9595bf..423770c22be 100755 --- a/tm2/pkg/overflow/overflow_template.sh +++ b/tm2/pkg/overflow/overflow_template.sh @@ -91,7 +91,6 @@ func Quotient${SIZE}(a, b int${SIZE}) (int${SIZE}, int${SIZE}, bool) { } c := a / b status := (c < 0) == ((a < 0) != (b < 0)) || (c == 0) // no sign check for 0 quotient - return c, a%b, status -} -" + return c, a % b, status +}" done diff --git a/tm2/pkg/p2p/discovery/discovery.go b/tm2/pkg/p2p/discovery/discovery.go index d884b118c75..7a9da3726c0 100644 --- a/tm2/pkg/p2p/discovery/discovery.go +++ b/tm2/pkg/p2p/discovery/discovery.go @@ -160,7 +160,7 @@ func (r *Reactor) Receive(chID byte, peer p2p.PeerConn, msgBytes []byte) { // Validate the message if err := msg.ValidateBasic(); err != nil { - r.Logger.Error("unable to validate discovery message", "err", err) + r.Logger.Warn("unable to validate discovery message", "err", err) return } @@ -168,7 +168,7 @@ func (r *Reactor) Receive(chID byte, peer p2p.PeerConn, msgBytes []byte) { switch msg := msg.(type) { case *Request: if err := r.handleDiscoveryRequest(peer); err != nil { - r.Logger.Error("unable to handle discovery request", "err", err) + r.Logger.Warn("unable to handle discovery request", "err", err) } case *Response: // Make the peers available for dialing on the switch @@ -186,9 +186,21 @@ func (r *Reactor) handleDiscoveryRequest(peer p2p.PeerConn) error { peers = make([]*types.NetAddress, 0, len(localPeers)) ) - // Exclude the private peers from being shared + // Exclude the private peers from being shared, + // as well as peers who are not dialable localPeers = slices.DeleteFunc(localPeers, func(p p2p.PeerConn) bool { - return p.IsPrivate() + var ( + // Private peers are peers whose information is kept private to the node + privatePeer = p.IsPrivate() + // The reason we don't validate the net address with .Routable() + // is because of legacy logic that supports local loopbacks as advertised + // peer addresses. Introducing a .Routable() constraint will filter all + // local loopback addresses shared by peers, and will cause local deployments + // (and unit test deployments) to break and require additional setup + invalidDialAddress = p.NodeInfo().DialAddress().Validate() != nil + ) + + return privatePeer || invalidDialAddress }) // Check if there is anything to share, @@ -207,7 +219,8 @@ func (r *Reactor) handleDiscoveryRequest(peer p2p.PeerConn) error { } for _, p := range localPeers { - peers = append(peers, p.SocketAddr()) + // Make sure only routable peers are shared + peers = append(peers, p.NodeInfo().DialAddress()) } // Create the response, and marshal diff --git a/tm2/pkg/p2p/discovery/discovery_test.go b/tm2/pkg/p2p/discovery/discovery_test.go index 17404e6039a..91741c648db 100644 --- a/tm2/pkg/p2p/discovery/discovery_test.go +++ b/tm2/pkg/p2p/discovery/discovery_test.go @@ -166,7 +166,7 @@ func TestReactor_DiscoveryResponse(t *testing.T) { slices.ContainsFunc(resp.Peers, func(addr *types.NetAddress) bool { for _, localP := range peers { - if localP.SocketAddr().Equals(*addr) { + if localP.NodeInfo().DialAddress().Equals(*addr) { return true } } @@ -317,7 +317,7 @@ func TestReactor_DiscoveryResponse(t *testing.T) { slices.ContainsFunc(resp.Peers, func(addr *types.NetAddress) bool { for _, localP := range peers { - if localP.SocketAddr().Equals(*addr) { + if localP.NodeInfo().DialAddress().Equals(*addr) { return true } } @@ -373,7 +373,7 @@ func TestReactor_DiscoveryResponse(t *testing.T) { peerAddrs := make([]*types.NetAddress, 0, len(peers)) for _, p := range peers { - peerAddrs = append(peerAddrs, p.SocketAddr()) + peerAddrs = append(peerAddrs, p.NodeInfo().DialAddress()) } // Prepare the message diff --git a/tm2/pkg/p2p/mock/peer.go b/tm2/pkg/p2p/mock/peer.go index e5a01952831..5be34121924 100644 --- a/tm2/pkg/p2p/mock/peer.go +++ b/tm2/pkg/p2p/mock/peer.go @@ -57,7 +57,7 @@ func GeneratePeers(t *testing.T, count int) []*Peer { }, NodeInfoFn: func() types.NodeInfo { return types.NodeInfo{ - PeerID: key.ID(), + NetAddress: addr, } }, SocketAddrFn: func() *types.NetAddress { diff --git a/tm2/pkg/p2p/peer.go b/tm2/pkg/p2p/peer.go index 135bf4b250c..dcca81ca097 100644 --- a/tm2/pkg/p2p/peer.go +++ b/tm2/pkg/p2p/peer.go @@ -160,7 +160,7 @@ func (p *peer) OnStop() { // ID returns the peer's ID - the hex encoded hash of its pubkey. func (p *peer) ID() types.ID { - return p.nodeInfo.PeerID + return p.nodeInfo.ID() } // NodeInfo returns a copy of the peer's NodeInfo. diff --git a/tm2/pkg/p2p/peer_test.go b/tm2/pkg/p2p/peer_test.go index a74ea9e96a4..75f5172ee66 100644 --- a/tm2/pkg/p2p/peer_test.go +++ b/tm2/pkg/p2p/peer_test.go @@ -243,7 +243,9 @@ func TestPeer_Properties(t *testing.T) { }, }, nodeInfo: types.NodeInfo{ - PeerID: id, + NetAddress: &types.NetAddress{ + ID: id, + }, }, connInfo: &ConnInfo{ Outbound: testCase.outbound, diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go index 5c1c37f7729..c96e429973e 100644 --- a/tm2/pkg/p2p/switch.go +++ b/tm2/pkg/p2p/switch.go @@ -1,11 +1,13 @@ package p2p import ( + "bytes" "context" "crypto/rand" + "encoding/binary" + "errors" "fmt" "math" - "math/big" "sync" "time" @@ -72,8 +74,9 @@ type MultiplexSwitch struct { privatePeers sync.Map // ID -> nothing; lookup table of peers who are not shared transport Transport - dialQueue *dial.Queue - events *events.Events + dialQueue *dial.Queue + dialNotify chan struct{} + events *events.Events } // NewMultiplexSwitch creates a new MultiplexSwitch with the given config. @@ -88,6 +91,7 @@ func NewMultiplexSwitch( peers: newSet(), transport: transport, dialQueue: dial.NewQueue(), + dialNotify: make(chan struct{}, 1), events: events.New(), maxInboundPeers: defaultCfg.MaxNumInboundPeers, maxOutboundPeers: defaultCfg.MaxNumOutboundPeers, @@ -262,13 +266,15 @@ func (sw *MultiplexSwitch) runDialLoop(ctx context.Context) { select { case <-ctx.Done(): sw.Logger.Debug("dial context canceled") - return + default: // Grab a dial item item := sw.dialQueue.Peek() if item == nil { - // Nothing to dial + // Nothing to dial, wait until something is + // added to the queue + sw.waitForPeersToDial(ctx) continue } @@ -352,7 +358,7 @@ func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { type backoffItem struct { lastDialTime time.Time - attempts int + attempts uint } var ( @@ -401,57 +407,76 @@ func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { peersToDial = make([]*types.NetAddress, 0) ) + // Gather addresses of persistent peers that are missing or + // not already in the dial queue sw.persistentPeers.Range(func(key, value any) bool { var ( id = key.(types.ID) addr = value.(*types.NetAddress) ) - // Check if the peer is part of the peer set - // or is scheduled for dialing - if peers.Has(id) || sw.dialQueue.Has(addr) { - return true + if !peers.Has(id) && !sw.dialQueue.Has(addr) { + peersToDial = append(peersToDial, addr) } - peersToDial = append(peersToDial, addr) - return true }) if len(peersToDial) == 0 { - // No persistent peers are missing + // No persistent peers need dialing return } - // Calculate the dial items + // Prepare dial items with the appropriate backoff dialItems := make([]dial.Item, 0, len(peersToDial)) - for _, p := range peersToDial { - item := getBackoffItem(p.ID) + for _, addr := range peersToDial { + item := getBackoffItem(addr.ID) + if item == nil { - dialItem := dial.Item{ - Time: time.Now(), - Address: p, - } + // First attempt + now := time.Now() + + dialItems = append(dialItems, + dial.Item{ + Time: now, + Address: addr, + }, + ) - dialItems = append(dialItems, dialItem) - setBackoffItem(p.ID, &backoffItem{dialItem.Time, 0}) + setBackoffItem(addr.ID, &backoffItem{ + lastDialTime: now, + attempts: 0, + }) continue } - setBackoffItem(p.ID, &backoffItem{ - lastDialTime: time.Now().Add( + // Subsequent attempt: apply backoff + var ( + attempts = item.attempts + 1 + dialTime = time.Now().Add( calculateBackoff( item.attempts, time.Second, 10*time.Minute, ), - ), - attempts: item.attempts + 1, + ) + ) + + dialItems = append(dialItems, + dial.Item{ + Time: dialTime, + Address: addr, + }, + ) + + setBackoffItem(addr.ID, &backoffItem{ + lastDialTime: dialTime, + attempts: attempts, }) } - // Add the peers to the dial queue + // Add these items to the dial queue sw.dialItems(dialItems...) } @@ -478,65 +503,68 @@ func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) { } } -// calculateBackoff calculates a backoff time, -// based on the number of attempts and range limits +// calculateBackoff calculates the backoff interval by exponentiating the base interval +// by the number of attempts. The returned interval is capped at maxInterval and has a +// jitter factor applied to it (+/- 10% of interval, max 10 sec). func calculateBackoff( - attempts int, - minTimeout time.Duration, - maxTimeout time.Duration, + attempts uint, + baseInterval time.Duration, + maxInterval time.Duration, ) time.Duration { - var ( - minTime = time.Second * 1 - maxTime = time.Second * 60 - multiplier = float64(2) // exponential + const ( + defaultBaseInterval = time.Second * 1 + defaultMaxInterval = time.Second * 60 ) - // Check the min limit - if minTimeout > 0 { - minTime = minTimeout + // Sanitize base interval parameter. + if baseInterval <= 0 { + baseInterval = defaultBaseInterval } - // Check the max limit - if maxTimeout > 0 { - maxTime = maxTimeout + // Sanitize max interval parameter. + if maxInterval <= 0 { + maxInterval = defaultMaxInterval } - // Sanity check the range - if minTime >= maxTime { - return maxTime + // Calculate the interval by exponentiating the base interval by the number of attempts. + interval := baseInterval << attempts + + // Cap the interval to the maximum interval. + if interval > maxInterval { + interval = maxInterval } - // Calculate the backoff duration - var ( - base = float64(minTime) - calculated = base * math.Pow(multiplier, float64(attempts)) - ) + // Below is the code to add a jitter factor to the interval. + // Read random bytes into an 8 bytes buffer (size of an int64). + var randBytes [8]byte + if _, err := rand.Read(randBytes[:]); err != nil { + return interval + } - // Attempt to calculate the jitter factor - n, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) - if err == nil { - jitterFactor := float64(n.Int64()) / float64(math.MaxInt64) // range [0, 1] + // Convert the random bytes to an int64. + var randInt64 int64 + _ = binary.Read(bytes.NewReader(randBytes[:]), binary.NativeEndian, &randInt64) - calculated = jitterFactor*(calculated-base) + base - } + // Calculate the random jitter multiplier (float between -1 and 1). + jitterMultiplier := float64(randInt64) / float64(math.MaxInt64) - // Prevent overflow for int64 (duration) cast - if calculated > float64(math.MaxInt64) { - return maxTime - } + const ( + maxJitterDuration = 10 * time.Second + maxJitterPercentage = 10 // 10% + ) - duration := time.Duration(calculated) + // Calculate the maximum jitter based on interval percentage. + maxJitter := interval * maxJitterPercentage / 100 - // Clamp the duration within bounds - if duration < minTime { - return minTime + // Cap the maximum jitter to the maximum duration. + if maxJitter > maxJitterDuration { + maxJitter = maxJitterDuration } - if duration > maxTime { - return maxTime - } + // Calculate the jitter. + jitter := time.Duration(float64(maxJitter) * jitterMultiplier) - return duration + return interval + jitter } // DialPeers adds the peers to the dial queue for async dialing. @@ -565,6 +593,7 @@ func (sw *MultiplexSwitch) DialPeers(peerAddrs ...*types.NetAddress) { } sw.dialQueue.Push(item) + sw.notifyAddPeerToDial() } } @@ -588,6 +617,7 @@ func (sw *MultiplexSwitch) dialItems(dialItems ...dial.Item) { } sw.dialQueue.Push(dialItem) + sw.notifyAddPeerToDial() } } @@ -612,50 +642,50 @@ func (sw *MultiplexSwitch) isPrivatePeer(id types.ID) bool { // and persisting them func (sw *MultiplexSwitch) runAcceptLoop(ctx context.Context) { for { - select { - case <-ctx.Done(): - sw.Logger.Debug("switch context close received") + p, err := sw.transport.Accept(ctx, sw.peerBehavior) - return + switch { + case err == nil: // ok + case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded): + // Upper context as been canceled/timeout + sw.Logger.Debug("switch context close received") + return // exit + case errors.As(err, &errTransportClosed): + // Underlaying transport as been closed + sw.Logger.Warn("cannot accept connection on closed transport, exiting") + return // exit default: - p, err := sw.transport.Accept(ctx, sw.peerBehavior) - if err != nil { - sw.Logger.Error( - "error encountered during peer connection accept", - "err", err, - ) + // An error occurred during accept, report and continue + sw.Logger.Error("error encountered during peer connection accept", "err", err) + continue + } - continue - } + // Ignore connection if we already have enough peers. + if in := sw.Peers().NumInbound(); in >= sw.maxInboundPeers { + sw.Logger.Info( + "Ignoring inbound connection: already have enough inbound peers", + "address", p.SocketAddr(), + "have", in, + "max", sw.maxInboundPeers, + ) - // Ignore connection if we already have enough peers. - if in := sw.Peers().NumInbound(); in >= sw.maxInboundPeers { - sw.Logger.Info( - "Ignoring inbound connection: already have enough inbound peers", - "address", p.SocketAddr(), - "have", in, - "max", sw.maxInboundPeers, - ) + sw.transport.Remove(p) + continue + } - sw.transport.Remove(p) + // There are open peer slots, add peers + if err := sw.addPeer(p); err != nil { + sw.transport.Remove(p) - continue + if p.IsRunning() { + _ = p.Stop() } - // There are open peer slots, add peers - if err := sw.addPeer(p); err != nil { - sw.transport.Remove(p) - - if p.IsRunning() { - _ = p.Stop() - } - - sw.Logger.Info( - "Ignoring inbound connection: error while adding peer", - "err", err, - "id", p.ID(), - ) - } + sw.Logger.Info( + "Ignoring inbound connection: error while adding peer", + "err", err, + "id", p.ID(), + ) } } } @@ -698,6 +728,20 @@ func (sw *MultiplexSwitch) addPeer(p PeerConn) error { return nil } +func (sw *MultiplexSwitch) notifyAddPeerToDial() { + select { + case sw.dialNotify <- struct{}{}: + default: + } +} + +func (sw *MultiplexSwitch) waitForPeersToDial(ctx context.Context) { + select { + case <-ctx.Done(): + case <-sw.dialNotify: + } +} + // logTelemetry logs the switch telemetry data // to global metrics funnels func (sw *MultiplexSwitch) logTelemetry() { diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go index 19a5db2efa5..e5f472cc28e 100644 --- a/tm2/pkg/p2p/switch_test.go +++ b/tm2/pkg/p2p/switch_test.go @@ -727,7 +727,7 @@ func TestMultiplexSwitch_DialPeers(t *testing.T) { // as the transport (node) p.NodeInfoFn = func() types.NodeInfo { return types.NodeInfo{ - PeerID: addr.ID, + NetAddress: &addr, } } @@ -823,3 +823,101 @@ func TestMultiplexSwitch_DialPeers(t *testing.T) { } }) } + +func TestCalculateBackoff(t *testing.T) { + t.Parallel() + + checkJitterRange := func(t *testing.T, expectedAbs, actual time.Duration) { + t.Helper() + require.LessOrEqual(t, actual, expectedAbs) + require.GreaterOrEqual(t, actual, expectedAbs*-1) + } + + // Test that the default jitter factor is 10% of the backoff duration. + t.Run("percentage jitter", func(t *testing.T) { + t.Parallel() + + for i := 0; i < 1000; i++ { + checkJitterRange(t, 100*time.Millisecond, calculateBackoff(0, time.Second, 10*time.Minute)-time.Second) + checkJitterRange(t, 200*time.Millisecond, calculateBackoff(1, time.Second, 10*time.Minute)-2*time.Second) + checkJitterRange(t, 400*time.Millisecond, calculateBackoff(2, time.Second, 10*time.Minute)-4*time.Second) + checkJitterRange(t, 800*time.Millisecond, calculateBackoff(3, time.Second, 10*time.Minute)-8*time.Second) + checkJitterRange(t, 1600*time.Millisecond, calculateBackoff(4, time.Second, 10*time.Minute)-16*time.Second) + } + }) + + // Test that the jitter factor is capped at 10 sec. + t.Run("capped jitter", func(t *testing.T) { + t.Parallel() + + for i := 0; i < 1000; i++ { + checkJitterRange(t, 10*time.Second, calculateBackoff(7, time.Second, 10*time.Minute)-128*time.Second) + checkJitterRange(t, 10*time.Second, calculateBackoff(10, time.Second, 20*time.Minute)-1024*time.Second) + checkJitterRange(t, 10*time.Second, calculateBackoff(20, time.Second, 300*time.Hour)-1048576*time.Second) + } + }) + + // Test that the backoff interval is based on the baseInterval. + t.Run("base interval", func(t *testing.T) { + t.Parallel() + + for i := 0; i < 1000; i++ { + checkJitterRange(t, 4800*time.Millisecond, calculateBackoff(4, 3*time.Second, 10*time.Minute)-48*time.Second) + checkJitterRange(t, 8*time.Second, calculateBackoff(3, 10*time.Second, 10*time.Minute)-80*time.Second) + checkJitterRange(t, 10*time.Second, calculateBackoff(5, 3*time.Hour, 100*time.Hour)-96*time.Hour) + } + }) + + // Test that the backoff interval is capped at maxInterval +/- jitter factor. + t.Run("max interval", func(t *testing.T) { + t.Parallel() + + for i := 0; i < 1000; i++ { + checkJitterRange(t, 100*time.Millisecond, calculateBackoff(10, 10*time.Hour, time.Second)-time.Second) + checkJitterRange(t, 1600*time.Millisecond, calculateBackoff(10, 10*time.Hour, 16*time.Second)-16*time.Second) + checkJitterRange(t, 10*time.Second, calculateBackoff(10, 10*time.Hour, 128*time.Second)-128*time.Second) + } + }) + + // Test parameters sanitization for base and max intervals. + t.Run("parameters sanitization", func(t *testing.T) { + t.Parallel() + + for i := 0; i < 1000; i++ { + checkJitterRange(t, 100*time.Millisecond, calculateBackoff(0, -10, -10)-time.Second) + checkJitterRange(t, 1600*time.Millisecond, calculateBackoff(4, -10, -10)-16*time.Second) + checkJitterRange(t, 10*time.Second, calculateBackoff(7, -10, 10*time.Minute)-128*time.Second) + } + }) +} + +func TestSwitchAcceptLoopTransportClosed(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var transportClosed bool + mockTransport := &mockTransport{ + acceptFn: func(context.Context, PeerBehavior) (PeerConn, error) { + transportClosed = true + return nil, errTransportClosed + }, + } + + sw := NewMultiplexSwitch(mockTransport) + + // Run the accept loop + done := make(chan struct{}) + go func() { + sw.runAcceptLoop(ctx) + close(done) // signal that accept loop as ended + }() + + select { + case <-time.After(time.Second * 2): + require.FailNow(t, "timeout while waiting for running loop to stop") + case <-done: + assert.True(t, transportClosed) + } +} diff --git a/tm2/pkg/p2p/transport.go b/tm2/pkg/p2p/transport.go index 9edef9a15e5..3d64a48f437 100644 --- a/tm2/pkg/p2p/transport.go +++ b/tm2/pkg/p2p/transport.go @@ -2,6 +2,7 @@ package p2p import ( "context" + goerrors "errors" "fmt" "io" "log/slog" @@ -22,7 +23,6 @@ const defaultHandshakeTimeout = 3 * time.Second var ( errTransportClosed = errors.New("transport is closed") - errTransportInactive = errors.New("transport is inactive") errDuplicateConnection = errors.New("duplicate peer connection") errPeerIDNodeInfoMismatch = errors.New("connection ID does not match node info ID") errPeerIDDialMismatch = errors.New("connection ID does not match dialed ID") @@ -75,7 +75,10 @@ func NewMultiplexTransport( mConfig conn.MConnConfig, logger *slog.Logger, ) *MultiplexTransport { + ctx, cancel := context.WithCancel(context.Background()) return &MultiplexTransport{ + ctx: ctx, + cancelFn: cancel, peerCh: make(chan peerInfo, 1), mConfig: mConfig, nodeInfo: nodeInfo, @@ -92,12 +95,6 @@ func (mt *MultiplexTransport) NetAddress() types.NetAddress { // Accept waits for a verified inbound Peer to connect, and returns it [BLOCKING] func (mt *MultiplexTransport) Accept(ctx context.Context, behavior PeerBehavior) (PeerConn, error) { - // Sanity check, no need to wait - // on an inactive transport - if mt.listener == nil { - return nil, errTransportInactive - } - select { case <-ctx.Done(): return nil, ctx.Err() @@ -159,21 +156,19 @@ func (mt *MultiplexTransport) Listen(addr types.NetAddress) error { // - find out which one has been given to us. tcpAddr, ok := ln.Addr().(*net.TCPAddr) if !ok { + ln.Close() return fmt.Errorf("error finding port (after listening on port 0): %w", err) } addr.Port = uint16(tcpAddr.Port) } - // Set up the context - mt.ctx, mt.cancelFn = context.WithCancel(context.Background()) - mt.netAddr = addr mt.listener = ln // Run the routine for accepting // incoming peer connections - go mt.runAcceptLoop() + go mt.runAcceptLoop(mt.ctx) return nil } @@ -183,60 +178,58 @@ func (mt *MultiplexTransport) Listen(addr types.NetAddress) error { // 1. accepted by the transport // 2. filtered // 3. upgraded (handshaked + verified) -func (mt *MultiplexTransport) runAcceptLoop() { +func (mt *MultiplexTransport) runAcceptLoop(ctx context.Context) { var wg sync.WaitGroup - defer func() { wg.Wait() // Wait for all process routines - close(mt.peerCh) }() - for { - select { - case <-mt.ctx.Done(): - mt.logger.Debug("transport accept context closed") + ctx, cancel := context.WithCancel(ctx) + defer cancel() // cancel sub-connection process - return + for { + // Accept an incoming peer connection + c, err := mt.listener.Accept() + + switch { + case err == nil: // ok + case goerrors.Is(err, net.ErrClosed): + // Listener has been closed, this is not recoverable. + mt.logger.Debug("listener has been closed") + return // exit default: - // Accept an incoming peer connection - c, err := mt.listener.Accept() + // An error occurred during accept, report and continue + mt.logger.Warn("accept p2p connection error", "err", err) + continue + } + + // Process the new connection asynchronously + wg.Add(1) + + go func(c net.Conn) { + defer wg.Done() + + info, err := mt.processConn(c, "") if err != nil { mt.logger.Error( - "unable to accept p2p connection", + "unable to process p2p connection", "err", err, ) - continue - } - - // Process the new connection asynchronously - wg.Add(1) - - go func(c net.Conn) { - defer wg.Done() + // Close the connection + _ = c.Close() - info, err := mt.processConn(c, "") - if err != nil { - mt.logger.Error( - "unable to process p2p connection", - "err", err, - ) - - // Close the connection - _ = c.Close() - - return - } + return + } - select { - case mt.peerCh <- info: - case <-mt.ctx.Done(): - // Give up if the transport was closed. - _ = c.Close() - } - }(c) - } + select { + case mt.peerCh <- info: + case <-ctx.Done(): + // Give up if the transport was closed. + _ = c.Close() + } + }(c) } } diff --git a/tm2/pkg/p2p/transport_test.go b/tm2/pkg/p2p/transport_test.go index 3eb3264ec2b..840eb974e76 100644 --- a/tm2/pkg/p2p/transport_test.go +++ b/tm2/pkg/p2p/transport_test.go @@ -122,14 +122,12 @@ func TestMultiplexTransport_Accept(t *testing.T) { transport := NewMultiplexTransport(ni, nk, mCfg, logger) - p, err := transport.Accept(context.Background(), nil) + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + p, err := transport.Accept(ctx, nil) assert.Nil(t, p) - assert.ErrorIs( - t, - err, - errTransportInactive, - ) + assert.ErrorIs(t, err, context.DeadlineExceeded) }) t.Run("transport closed", func(t *testing.T) { @@ -239,7 +237,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { ni := types.NodeInfo{ Network: network, // common network - PeerID: id, + NetAddress: na, Version: "v1.0.0-rc.0", Moniker: fmt.Sprintf("node-%d", index), VersionSet: make(versionset.VersionSet, 0), // compatible version set @@ -319,7 +317,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { ni := types.NodeInfo{ Network: chainID, - PeerID: id, + NetAddress: na, Version: "v1.0.0-rc.0", Moniker: fmt.Sprintf("node-%d", index), VersionSet: make(versionset.VersionSet, 0), // compatible version set @@ -391,7 +389,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { ni := types.NodeInfo{ Network: network, // common network - PeerID: key.ID(), + NetAddress: na, Version: "v1.0.0-rc.0", Moniker: fmt.Sprintf("node-%d", index), VersionSet: make(versionset.VersionSet, 0), // compatible version set @@ -469,7 +467,7 @@ func TestMultiplexTransport_Accept(t *testing.T) { ni := types.NodeInfo{ Network: network, // common network - PeerID: key.ID(), + NetAddress: na, Version: "v1.0.0-rc.0", Moniker: fmt.Sprintf("node-%d", index), VersionSet: make(versionset.VersionSet, 0), // compatible version set diff --git a/tm2/pkg/p2p/types/node_info.go b/tm2/pkg/p2p/types/node_info.go index 8452cb43cb8..4080ff2d8aa 100644 --- a/tm2/pkg/p2p/types/node_info.go +++ b/tm2/pkg/p2p/types/node_info.go @@ -14,7 +14,6 @@ const ( ) var ( - ErrInvalidPeerID = errors.New("invalid peer ID") ErrInvalidVersion = errors.New("invalid node version") ErrInvalidMoniker = errors.New("invalid node moniker") ErrInvalidRPCAddress = errors.New("invalid node RPC address") @@ -30,8 +29,8 @@ type NodeInfo struct { // Set of protocol versions VersionSet versionset.VersionSet `json:"version_set"` - // Unique peer identifier - PeerID ID `json:"id"` + // The advertised net address of the peer + NetAddress *NetAddress `json:"net_address"` // Check compatibility. // Channels are HexBytes so easier to read as JSON @@ -54,12 +53,27 @@ type NodeInfoOther struct { // Validate checks the self-reported NodeInfo is safe. // It returns an error if there // are too many Channels, if there are any duplicate Channels, -// if the ListenAddr is malformed, or if the ListenAddr is a host name +// if the NetAddress is malformed, or if the NetAddress is a host name // that can not be resolved to some IP func (info NodeInfo) Validate() error { - // Validate the ID - if err := info.PeerID.Validate(); err != nil { - return fmt.Errorf("%w, %w", ErrInvalidPeerID, err) + // There are a few checks that need to be performed when validating + // the node info's net address: + // - the ID needs to be valid + // - the FORMAT of the net address needs to be valid + // + // The key nuance here is that the net address is not being validated + // for its "dialability", but whether it's of the correct format. + // + // Unspecified IPs are tolerated (ex. 0.0.0.0 or ::), + // because of legacy logic that assumes node info + // can have unspecified IPs (ex. no external address is set, use + // the listen address which is bound to 0.0.0.0). + // + // These types of IPs are caught during the + // real peer info sharing process, since they are undialable + _, err := NewNetAddressFromString(NetAddressString(info.NetAddress.ID, info.NetAddress.DialString())) + if err != nil { + return fmt.Errorf("invalid net address in node info, %w", err) } // Validate Version @@ -100,7 +114,12 @@ func (info NodeInfo) Validate() error { // ID returns the local node ID func (info NodeInfo) ID() ID { - return info.PeerID + return info.NetAddress.ID +} + +// DialAddress is the advertised peer dial address (share-able) +func (info NodeInfo) DialAddress() *NetAddress { + return info.NetAddress } // CompatibleWith checks if two NodeInfo are compatible with each other. diff --git a/tm2/pkg/p2p/types/node_info_test.go b/tm2/pkg/p2p/types/node_info_test.go index d03d77e608f..575d8ae5fbd 100644 --- a/tm2/pkg/p2p/types/node_info_test.go +++ b/tm2/pkg/p2p/types/node_info_test.go @@ -2,23 +2,43 @@ package types import ( "fmt" + "net" "testing" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/versionset" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNodeInfo_Validate(t *testing.T) { t.Parallel() + generateNetAddress := func() *NetAddress { + var ( + key = GenerateNodeKey() + address = "127.0.0.1:8080" + ) + + tcpAddr, err := net.ResolveTCPAddr("tcp", address) + require.NoError(t, err) + + addr, err := NewNetAddress(key.ID(), tcpAddr) + require.NoError(t, err) + + return addr + } + t.Run("invalid peer ID", func(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: "", // zero + NetAddress: &NetAddress{ + ID: "", // zero + }, } - assert.ErrorIs(t, info.Validate(), ErrInvalidPeerID) + assert.ErrorIs(t, info.Validate(), crypto.ErrZeroID) }) t.Run("invalid version", func(t *testing.T) { @@ -47,8 +67,8 @@ func TestNodeInfo_Validate(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: GenerateNodeKey().ID(), - Version: testCase.version, + NetAddress: generateNetAddress(), + Version: testCase.version, } assert.ErrorIs(t, info.Validate(), ErrInvalidVersion) @@ -86,8 +106,8 @@ func TestNodeInfo_Validate(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: GenerateNodeKey().ID(), - Moniker: testCase.moniker, + NetAddress: generateNetAddress(), + Moniker: testCase.moniker, } assert.ErrorIs(t, info.Validate(), ErrInvalidMoniker) @@ -121,8 +141,8 @@ func TestNodeInfo_Validate(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: GenerateNodeKey().ID(), - Moniker: "valid moniker", + NetAddress: generateNetAddress(), + Moniker: "valid moniker", Other: NodeInfoOther{ RPCAddress: testCase.rpcAddress, }, @@ -162,9 +182,9 @@ func TestNodeInfo_Validate(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: GenerateNodeKey().ID(), - Moniker: "valid moniker", - Channels: testCase.channels, + NetAddress: generateNetAddress(), + Moniker: "valid moniker", + Channels: testCase.channels, } assert.ErrorIs(t, info.Validate(), testCase.expectedErr) @@ -176,9 +196,9 @@ func TestNodeInfo_Validate(t *testing.T) { t.Parallel() info := &NodeInfo{ - PeerID: GenerateNodeKey().ID(), - Moniker: "valid moniker", - Channels: []byte{10, 20, 30}, + NetAddress: generateNetAddress(), + Moniker: "valid moniker", + Channels: []byte{10, 20, 30}, Other: NodeInfoOther{ RPCAddress: "0.0.0.0:26657", }, diff --git a/tm2/pkg/sdk/auth/abci.go b/tm2/pkg/sdk/auth/abci.go index 86cbf962fad..9cbebe676e9 100644 --- a/tm2/pkg/sdk/auth/abci.go +++ b/tm2/pkg/sdk/auth/abci.go @@ -14,6 +14,6 @@ func EndBlocker(ctx sdk.Context, gk GasPriceKeeperI) { // InitChainer is called in the InitChain(), it set the initial gas price in the // GasPriceKeeper store // for the next gas price -func InitChainer(ctx sdk.Context, gk GasPriceKeeper, gp std.GasPrice) { +func InitChainer(ctx sdk.Context, gk GasPriceKeeperI, gp std.GasPrice) { gk.SetGasPrice(ctx, gp) } diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index f941f398b17..f2634bcb9ce 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -67,7 +67,7 @@ func NewAnteHandler(ak AccountKeeper, bank BankKeeperI, sigGasConsumer Signature } } - newCtx = SetGasMeter(simulate, ctx, tx.Fee.GasWanted) + newCtx = SetGasMeter(ctx, tx.Fee.GasWanted) // AnteHandlers must have their own defer/recover in order for the BaseApp // to know how much gas was used! This is because the GasMeter is created in @@ -322,7 +322,8 @@ func DeductFees(bank BankKeeperI, ctx sdk.Context, acc std.Account, fees std.Coi )) } - err := bank.SendCoins(ctx, acc.GetAddress(), FeeCollectorAddress(), fees) + // Sending coins is unrestricted to pay for gas fees + err := bank.SendCoinsUnrestricted(ctx, acc.GetAddress(), FeeCollectorAddress(), fees) if err != nil { return abciResult(err) } @@ -406,10 +407,10 @@ func EnsureSufficientMempoolFees(ctx sdk.Context, fee std.Fee) sdk.Result { } // SetGasMeter returns a new context with a gas meter set from a given context. -func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit int64) sdk.Context { +func SetGasMeter(ctx sdk.Context, gasLimit int64) sdk.Context { // In various cases such as simulation and during the genesis block, we do not // meter any gas utilization. - if simulate || ctx.BlockHeight() == 0 { + if ctx.BlockHeight() == 0 { return ctx.WithGasMeter(store.NewInfiniteGasMeter()) } diff --git a/tm2/pkg/sdk/auth/ante_test.go b/tm2/pkg/sdk/auth/ante_test.go index 430954a0867..121ddb2fd59 100644 --- a/tm2/pkg/sdk/auth/ante_test.go +++ b/tm2/pkg/sdk/auth/ante_test.go @@ -65,7 +65,7 @@ func TestAnteHandlerSigErrors(t *testing.T) { // setup env := setupTestEnv() ctx := env.ctx - anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) + anteHandler := NewAnteHandler(env.acck, env.bankk, DefaultSigVerificationGasConsumer, defaultAnteOptions()) // keys and addresses priv1, _, addr1 := tu.KeyTestPubAddr() @@ -114,7 +114,7 @@ func TestAnteHandlerAccountNumbers(t *testing.T) { // setup env := setupTestEnv() - anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) + anteHandler := NewAnteHandler(env.acck, env.bankk, DefaultSigVerificationGasConsumer, defaultAnteOptions()) ctx := env.ctx // keys and addresses @@ -173,7 +173,7 @@ func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { // setup env := setupTestEnv() - anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) + anteHandler := NewAnteHandler(env.acck, env.bankk, DefaultSigVerificationGasConsumer, defaultAnteOptions()) ctx := env.ctx header := ctx.BlockHeader().(*bft.Header) header.Height = 0 @@ -234,7 +234,7 @@ func TestAnteHandlerSequences(t *testing.T) { // setup env := setupTestEnv() - anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) + anteHandler := NewAnteHandler(env.acck, env.bankk, DefaultSigVerificationGasConsumer, defaultAnteOptions()) ctx := env.ctx // keys and addresses @@ -314,7 +314,7 @@ func TestAnteHandlerFees(t *testing.T) { // setup env := setupTestEnv() ctx := env.ctx - anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) + anteHandler := NewAnteHandler(env.acck, env.bankk, DefaultSigVerificationGasConsumer, defaultAnteOptions()) // keys and addresses priv1, _, addr1 := tu.KeyTestPubAddr() @@ -338,7 +338,7 @@ func TestAnteHandlerFees(t *testing.T) { env.acck.SetAccount(ctx, acc1) checkInvalidTx(t, anteHandler, ctx, tx, false, std.InsufficientFundsError{}) - collector := env.bank.(DummyBankKeeper).acck.GetAccount(ctx, FeeCollectorAddress()) + collector := env.bankk.(DummyBankKeeper).acck.GetAccount(ctx, FeeCollectorAddress()) require.Nil(t, collector) require.Equal(t, env.acck.GetAccount(ctx, addr1).GetCoins().AmountOf("atom"), int64(149)) @@ -346,7 +346,7 @@ func TestAnteHandlerFees(t *testing.T) { env.acck.SetAccount(ctx, acc1) checkValidTx(t, anteHandler, ctx, tx, false) - require.Equal(t, env.bank.(DummyBankKeeper).acck.GetAccount(ctx, FeeCollectorAddress()).GetCoins().AmountOf("atom"), int64(150)) + require.Equal(t, env.bankk.(DummyBankKeeper).acck.GetAccount(ctx, FeeCollectorAddress()).GetCoins().AmountOf("atom"), int64(150)) require.Equal(t, env.acck.GetAccount(ctx, addr1).GetCoins().AmountOf("atom"), int64(0)) } @@ -356,7 +356,7 @@ func TestAnteHandlerMemoGas(t *testing.T) { // setup env := setupTestEnv() - anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) + anteHandler := NewAnteHandler(env.acck, env.bankk, DefaultSigVerificationGasConsumer, defaultAnteOptions()) ctx := env.ctx // keys and addresses @@ -398,7 +398,7 @@ func TestAnteHandlerMultiSigner(t *testing.T) { // setup env := setupTestEnv() - anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) + anteHandler := NewAnteHandler(env.acck, env.bankk, DefaultSigVerificationGasConsumer, defaultAnteOptions()) ctx := env.ctx // keys and addresses @@ -450,7 +450,7 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { // setup env := setupTestEnv() - anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) + anteHandler := NewAnteHandler(env.acck, env.bankk, DefaultSigVerificationGasConsumer, defaultAnteOptions()) ctx := env.ctx // keys and addresses @@ -538,7 +538,7 @@ func TestAnteHandlerSetPubKey(t *testing.T) { // setup env := setupTestEnv() - anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) + anteHandler := NewAnteHandler(env.acck, env.bankk, DefaultSigVerificationGasConsumer, defaultAnteOptions()) ctx := env.ctx // keys and addresses @@ -753,7 +753,7 @@ func TestAnteHandlerSigLimitExceeded(t *testing.T) { // setup env := setupTestEnv() - anteHandler := NewAnteHandler(env.acck, env.bank, DefaultSigVerificationGasConsumer, defaultAnteOptions()) + anteHandler := NewAnteHandler(env.acck, env.bankk, DefaultSigVerificationGasConsumer, defaultAnteOptions()) ctx := env.ctx // keys and addresses @@ -830,7 +830,7 @@ func TestCustomSignatureVerificationGasConsumer(t *testing.T) { // setup env := setupTestEnv() // setup an ante handler that only accepts PubKeyEd25519 - anteHandler := NewAnteHandler(env.acck, env.bank, func(meter store.GasMeter, sig []byte, pubkey crypto.PubKey, params Params) sdk.Result { + anteHandler := NewAnteHandler(env.acck, env.bankk, func(meter store.GasMeter, sig []byte, pubkey crypto.PubKey, params Params) sdk.Result { switch pubkey := pubkey.(type) { case ed25519.PubKeyEd25519: meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") diff --git a/tm2/pkg/sdk/auth/genesis.go b/tm2/pkg/sdk/auth/genesis.go index c863c237a41..49799ad8b15 100644 --- a/tm2/pkg/sdk/auth/genesis.go +++ b/tm2/pkg/sdk/auth/genesis.go @@ -1,19 +1,38 @@ package auth import ( + "fmt" + "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/sdk" ) -// InitGenesis - Init store state from genesis data -func (ak AccountKeeper) InitGenesis(ctx sdk.Context, data GenesisState) { +// GenesisState - all state that must be provided at genesis +type GenesisState struct { + Params Params `json:"params" yaml:"params"` +} + +// NewGenesisState - Create a new genesis state +func NewGenesisState(params Params) GenesisState { + return GenesisState{params} +} + +// DefaultGenesisState - Return a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(DefaultParams()) +} + +// ValidateGenesis performs basic validation of genesis data returning an +// error for any failed validation criteria. +func ValidateGenesis(data GenesisState) error { if amino.DeepEqual(data, GenesisState{}) { - if err := ak.SetParams(ctx, DefaultParams()); err != nil { - panic(err) - } - return + return fmt.Errorf("auth genesis state cannot be empty") } + return data.Params.Validate() +} +// InitGenesis - Init store state from genesis data +func (ak AccountKeeper) InitGenesis(ctx sdk.Context, data GenesisState) { if err := ValidateGenesis(data); err != nil { panic(err) } diff --git a/tm2/pkg/sdk/auth/keeper.go b/tm2/pkg/sdk/auth/keeper.go index fc83997fdc4..871d2dd3722 100644 --- a/tm2/pkg/sdk/auth/keeper.go +++ b/tm2/pkg/sdk/auth/keeper.go @@ -17,8 +17,10 @@ import ( type AccountKeeper struct { // The (unexposed) key used to access the store from the Context. key store.StoreKey - // The keeper used to store auth parameters - paramk params.ParamsKeeper + + // store module parameters + prmk params.ParamsKeeperI + // The prototypical Account constructor. proto func() std.Account } @@ -26,12 +28,12 @@ type AccountKeeper struct { // NewAccountKeeper returns a new AccountKeeper that uses go-amino to // (binary) encode and decode concrete std.Accounts. func NewAccountKeeper( - key store.StoreKey, pk params.ParamsKeeper, proto func() std.Account, + key store.StoreKey, pk params.ParamsKeeperI, proto func() std.Account, ) AccountKeeper { return AccountKeeper{ - key: key, - paramk: pk, - proto: proto, + key: key, + prmk: pk, + proto: proto, } } diff --git a/tm2/pkg/sdk/auth/params.go b/tm2/pkg/sdk/auth/params.go index fda85c7a3d6..428066b3817 100644 --- a/tm2/pkg/sdk/auth/params.go +++ b/tm2/pkg/sdk/auth/params.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -13,25 +14,27 @@ type AuthParamsContextKey struct{} // Default parameter values const ( - DefaultMaxMemoBytes int64 = 65536 - DefaultTxSigLimit int64 = 7 - DefaultTxSizeCostPerByte int64 = 10 - DefaultSigVerifyCostED25519 int64 = 590 - DefaultSigVerifyCostSecp256k1 int64 = 1000 + DefaultMaxMemoBytes int64 = 65536 + DefaultTxSigLimit int64 = 7 + DefaultTxSizeCostPerByte int64 = 10 + DefaultSigVerifyCostED25519 int64 = 590 + DefaultSigVerifyCostSecp256k1 int64 = 1000 + DefaultGasPricesChangeCompressor int64 = 10 DefaultTargetGasRatio int64 = 70 // 70% of the MaxGas in a block ) // Params defines the parameters for the auth module. type Params struct { - MaxMemoBytes int64 `json:"max_memo_bytes" yaml:"max_memo_bytes"` - TxSigLimit int64 `json:"tx_sig_limit" yaml:"tx_sig_limit"` - TxSizeCostPerByte int64 `json:"tx_size_cost_per_byte" yaml:"tx_size_cost_per_byte"` - SigVerifyCostED25519 int64 `json:"sig_verify_cost_ed25519" yaml:"sig_verify_cost_ed25519"` - SigVerifyCostSecp256k1 int64 `json:"sig_verify_cost_secp256k1" yaml:"sig_verify_cost_secp256k1"` - GasPricesChangeCompressor int64 `json:"gas_price_change_compressor" yaml:"gas_price_change_compressor"` - TargetGasRatio int64 `json:"target_gas_ratio" yaml:"target_gas_ratio"` - InitialGasPrice std.GasPrice `json:"initial_gasprice"` + MaxMemoBytes int64 `json:"max_memo_bytes" yaml:"max_memo_bytes"` + TxSigLimit int64 `json:"tx_sig_limit" yaml:"tx_sig_limit"` + TxSizeCostPerByte int64 `json:"tx_size_cost_per_byte" yaml:"tx_size_cost_per_byte"` + SigVerifyCostED25519 int64 `json:"sig_verify_cost_ed25519" yaml:"sig_verify_cost_ed25519"` + SigVerifyCostSecp256k1 int64 `json:"sig_verify_cost_secp256k1" yaml:"sig_verify_cost_secp256k1"` + GasPricesChangeCompressor int64 `json:"gas_price_change_compressor" yaml:"gas_price_change_compressor"` + TargetGasRatio int64 `json:"target_gas_ratio" yaml:"target_gas_ratio"` + InitialGasPrice std.GasPrice `json:"initial_gasprice"` + UnrestrictedAddrs []crypto.Address `json:"unrestricted_addrs" yaml:"unrestricted_addrs"` } // NewParams creates a new Params object @@ -56,15 +59,15 @@ func (p Params) Equals(p2 Params) bool { // DefaultParams returns a default set of parameters. func DefaultParams() Params { - return Params{ - MaxMemoBytes: DefaultMaxMemoBytes, - TxSigLimit: DefaultTxSigLimit, - TxSizeCostPerByte: DefaultTxSizeCostPerByte, - SigVerifyCostED25519: DefaultSigVerifyCostED25519, - SigVerifyCostSecp256k1: DefaultSigVerifyCostSecp256k1, - GasPricesChangeCompressor: DefaultGasPricesChangeCompressor, - TargetGasRatio: DefaultTargetGasRatio, - } + return NewParams( + DefaultMaxMemoBytes, + DefaultTxSigLimit, + DefaultTxSizeCostPerByte, + DefaultSigVerifyCostED25519, + DefaultSigVerifyCostSecp256k1, + DefaultGasPricesChangeCompressor, + DefaultTargetGasRatio, + ) } // String implements the stringer interface. @@ -83,16 +86,19 @@ func (p Params) String() string { } func (p Params) Validate() error { - if p.TxSigLimit == 0 { + if p.MaxMemoBytes <= 0 { + return fmt.Errorf("invalid max memo bytes: %d", p.MaxMemoBytes) + } + if p.TxSigLimit <= 0 { return fmt.Errorf("invalid tx signature limit: %d", p.TxSigLimit) } - if p.SigVerifyCostED25519 == 0 { + if p.SigVerifyCostED25519 <= 0 { return fmt.Errorf("invalid ED25519 signature verification cost: %d", p.SigVerifyCostED25519) } - if p.SigVerifyCostSecp256k1 == 0 { + if p.SigVerifyCostSecp256k1 <= 0 { return fmt.Errorf("invalid SECK256k1 signature verification cost: %d", p.SigVerifyCostSecp256k1) } - if p.TxSizeCostPerByte == 0 { + if p.TxSizeCostPerByte <= 0 { return fmt.Errorf("invalid tx size cost per byte: %d", p.TxSizeCostPerByte) } if p.GasPricesChangeCompressor <= 0 { @@ -108,20 +114,16 @@ func (ak AccountKeeper) SetParams(ctx sdk.Context, params Params) error { if err := params.Validate(); err != nil { return err } - err := ak.paramk.SetParams(ctx, ModuleName, params) - return err + ak.prmk.SetStruct(ctx, "p", params) + return nil } func (ak AccountKeeper) GetParams(ctx sdk.Context) Params { - params := &Params{} - - ok, err := ak.paramk.GetParams(ctx, ModuleName, params) + params := Params{} + ak.prmk.GetStruct(ctx, "p", ¶ms) + return params +} - if !ok { - panic("params key " + ModuleName + " does not exist") - } - if err != nil { - panic(err.Error()) - } - return *params +func (ak AccountKeeper) WillSetParam(ctx sdk.Context, key string, value interface{}) { + // XXX validate input? } diff --git a/tm2/pkg/sdk/auth/test_common.go b/tm2/pkg/sdk/auth/test_common.go index e0a6316bead..ec8880f5b2f 100644 --- a/tm2/pkg/sdk/auth/test_common.go +++ b/tm2/pkg/sdk/auth/test_common.go @@ -14,10 +14,10 @@ import ( ) type testEnv struct { - ctx sdk.Context - acck AccountKeeper - bank BankKeeperI - gk GasPriceKeeper + ctx sdk.Context + acck AccountKeeper + bankk BankKeeperI + gk GasPriceKeeper } func setupTestEnv() testEnv { @@ -28,12 +28,15 @@ func setupTestEnv() testEnv { ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(authCapKey, iavl.StoreConstructor, db) ms.LoadLatestVersion() + prmk := params.NewParamsKeeper(authCapKey) - paramk := params.NewParamsKeeper(authCapKey, "") - acck := NewAccountKeeper(authCapKey, paramk, std.ProtoBaseAccount) - bank := NewDummyBankKeeper(acck) + acck := NewAccountKeeper(authCapKey, prmk.ForModule(ModuleName), std.ProtoBaseAccount) + bankk := NewDummyBankKeeper(acck, prmk.ForModule("dummybank")) gk := NewGasPriceKeeper(authCapKey) + prmk.Register(ModuleName, acck) + prmk.Register("dummybank", bankk) + ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{Height: 1, ChainID: "test-chain-id"}, log.NewNoopLogger()) ctx = ctx.WithValue(AuthParamsContextKey{}, DefaultParams()) ctx = ctx.WithConsensusParams(&abci.ConsensusParams{ @@ -49,7 +52,7 @@ func setupTestEnv() testEnv { }, }) - return testEnv{ctx: ctx, acck: acck, bank: bank, gk: gk} + return testEnv{ctx: ctx, acck: acck, bankk: bankk, gk: gk} } // DummyBankKeeper defines a supply keeper used only for testing to avoid @@ -59,16 +62,20 @@ type DummyBankKeeper struct { } // NewDummyBankKeeper creates a DummyBankKeeper instance -func NewDummyBankKeeper(acck AccountKeeper) DummyBankKeeper { +func NewDummyBankKeeper(acck AccountKeeper, prmk params.ParamsKeeperI) DummyBankKeeper { return DummyBankKeeper{acck} } +func (bankk DummyBankKeeper) SendCoinsUnrestricted(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { + return bankk.SendCoins(ctx, fromAddr, toAddr, amt) +} + // SendCoins for the dummy supply keeper -func (bank DummyBankKeeper) SendCoins(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { - fromAcc := bank.acck.GetAccount(ctx, fromAddr) - toAcc := bank.acck.GetAccount(ctx, toAddr) +func (bankk DummyBankKeeper) SendCoins(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { + fromAcc := bankk.acck.GetAccount(ctx, fromAddr) + toAcc := bankk.acck.GetAccount(ctx, toAddr) if toAcc == nil { - toAcc = bank.acck.NewAccountWithAddress(ctx, toAddr) + toAcc = bankk.acck.NewAccountWithAddress(ctx, toAddr) } newFromCoins := fromAcc.GetCoins().SubUnsafe(amt) @@ -79,11 +86,14 @@ func (bank DummyBankKeeper) SendCoins(ctx sdk.Context, fromAddr crypto.Address, if err := fromAcc.SetCoins(newFromCoins); err != nil { return std.ErrInternal(err.Error()) } - bank.acck.SetAccount(ctx, fromAcc) + bankk.acck.SetAccount(ctx, fromAcc) if err := toAcc.SetCoins(newToCoins); err != nil { return std.ErrInternal(err.Error()) } - bank.acck.SetAccount(ctx, toAcc) + bankk.acck.SetAccount(ctx, toAcc) return nil } + +// WillSetParam checks if the key contains the module's parameter key prefix and updates the module parameter accordingly. +func (bankk DummyBankKeeper) WillSetParam(ctx sdk.Context, key string, value interface{}) { return } diff --git a/tm2/pkg/sdk/auth/types.go b/tm2/pkg/sdk/auth/types.go index 3fb2d10fbb5..4965122bf67 100644 --- a/tm2/pkg/sdk/auth/types.go +++ b/tm2/pkg/sdk/auth/types.go @@ -22,6 +22,7 @@ var _ AccountKeeperI = AccountKeeper{} // Limited interface only needed for auth. type BankKeeperI interface { SendCoins(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error + SendCoinsUnrestricted(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error } type GasPriceKeeperI interface { @@ -31,24 +32,3 @@ type GasPriceKeeperI interface { } var _ GasPriceKeeperI = GasPriceKeeper{} - -// GenesisState - all auth state that must be provided at genesis -type GenesisState struct { - Params Params `json:"params"` -} - -// NewGenesisState - Create a new genesis state -func NewGenesisState(params Params) GenesisState { - return GenesisState{params} -} - -// DefaultGenesisState - Return a default genesis state -func DefaultGenesisState() GenesisState { - return NewGenesisState(DefaultParams()) -} - -// ValidateGenesis performs basic validation of auth genesis data returning an -// error for any failed validation criteria. -func ValidateGenesis(data GenesisState) error { - return data.Params.Validate() -} diff --git a/tm2/pkg/sdk/bank/common_test.go b/tm2/pkg/sdk/bank/common_test.go index c8210be7175..a63c22e0742 100644 --- a/tm2/pkg/sdk/bank/common_test.go +++ b/tm2/pkg/sdk/bank/common_test.go @@ -16,9 +16,10 @@ import ( ) type testEnv struct { - ctx sdk.Context - bank BankKeeper - acck auth.AccountKeeper + ctx sdk.Context + bankk BankKeeper + acck auth.AccountKeeper + prmk params.ParamsKeeper } func setupTestEnv() testEnv { @@ -29,13 +30,14 @@ func setupTestEnv() testEnv { ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(authCapKey, iavl.StoreConstructor, db) ms.LoadLatestVersion() - paramk := params.NewParamsKeeper(authCapKey, "") ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{ChainID: "test-chain-id"}, log.NewNoopLogger()) - acck := auth.NewAccountKeeper( - authCapKey, paramk, std.ProtoBaseAccount, - ) - bank := NewBankKeeper(acck) + prmk := params.NewParamsKeeper(authCapKey) + acck := auth.NewAccountKeeper(authCapKey, prmk.ForModule(auth.ModuleName), std.ProtoBaseAccount) + bankk := NewBankKeeper(acck, prmk.ForModule(ModuleName)) - return testEnv{ctx: ctx, bank: bank, acck: acck} + prmk.Register(auth.ModuleName, acck) + prmk.Register(ModuleName, bankk) + + return testEnv{ctx: ctx, bankk: bankk, acck: acck, prmk: prmk} } diff --git a/tm2/pkg/sdk/bank/genesis.go b/tm2/pkg/sdk/bank/genesis.go new file mode 100644 index 00000000000..ee6a474d693 --- /dev/null +++ b/tm2/pkg/sdk/bank/genesis.go @@ -0,0 +1,44 @@ +package bank + +import ( + "github.com/gnolang/gno/tm2/pkg/sdk" +) + +// GenesisState - all state that must be provided at genesis +type GenesisState struct { + Params Params `json:"params" yaml:"params"` +} + +// NewGenesisState - Create a new genesis state +func NewGenesisState(params Params) GenesisState { + return GenesisState{params} +} + +// DefaultGenesisState - Return a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(DefaultParams()) +} + +// ValidateGenesis performs basic validation of genesis data returning an +// error for any failed validation criteria. +func ValidateGenesis(data GenesisState) error { + return data.Params.Validate() +} + +// InitGenesis - Init store state from genesis data +func (bank BankKeeper) InitGenesis(ctx sdk.Context, data GenesisState) { + if err := ValidateGenesis(data); err != nil { + panic(err) + } + + if err := bank.SetParams(ctx, data.Params); err != nil { + panic(err) + } +} + +// ExportGenesis returns a GenesisState for a given context and keeper +func (bank BankKeeper) ExportGenesis(ctx sdk.Context) GenesisState { + params := bank.GetParams(ctx) + + return NewGenesisState(params) +} diff --git a/tm2/pkg/sdk/bank/handler_test.go b/tm2/pkg/sdk/bank/handler_test.go index 85fc68fc304..41fc80b8431 100644 --- a/tm2/pkg/sdk/bank/handler_test.go +++ b/tm2/pkg/sdk/bank/handler_test.go @@ -28,7 +28,7 @@ func TestBalances(t *testing.T) { t.Parallel() env := setupTestEnv() - h := NewHandler(env.bank) + h := NewHandler(env.bankk) _, _, addr := tu.KeyTestPubAddr() req := abci.RequestQuery{ @@ -58,7 +58,7 @@ func TestQuerierRouteNotFound(t *testing.T) { t.Parallel() env := setupTestEnv() - h := NewHandler(env.bank) + h := NewHandler(env.bankk) req := abci.RequestQuery{ Path: "bank/notfound", Data: []byte{}, diff --git a/tm2/pkg/sdk/bank/keeper.go b/tm2/pkg/sdk/bank/keeper.go index f98e6b3e225..2b5057ea492 100644 --- a/tm2/pkg/sdk/bank/keeper.go +++ b/tm2/pkg/sdk/bank/keeper.go @@ -7,6 +7,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/sdk/params" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -21,9 +22,13 @@ type BankKeeperI interface { SubtractCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) (std.Coins, error) AddCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) (std.Coins, error) SetCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) error + SendCoinsUnrestricted(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error + + InitGenesis(ctx sdk.Context, data GenesisState) + GetParams(ctx sdk.Context) Params } -var _ BankKeeperI = BankKeeper{} +var _ BankKeeperI = &BankKeeper{} // BankKeeper only allows transfers between accounts without the possibility of // creating coins. It implements the BankKeeperI interface. @@ -31,14 +36,46 @@ type BankKeeper struct { ViewKeeper acck auth.AccountKeeper + // The keeper used to store parameters + prmk params.ParamsKeeperI } // NewBankKeeper returns a new BankKeeper. -func NewBankKeeper(acck auth.AccountKeeper) BankKeeper { +func NewBankKeeper(acck auth.AccountKeeper, pk params.ParamsKeeperI) BankKeeper { return BankKeeper{ ViewKeeper: NewViewKeeper(acck), acck: acck, + prmk: pk, + } +} + +// This is a convenience function for manually setting the restricted denoms. +// Useful for testing and initchain setup. +// The ParamKeeper will call WillSetRestrictedDenoms() before writing. +func (bank BankKeeper) SetRestrictedDenoms(ctx sdk.Context, restrictedDenoms []string) { + bank.prmk.SetStrings(ctx, "p:restricted_denoms", restrictedDenoms) +} + +// This will get called whenever the restricted denoms parameter is changed. +func (bank BankKeeper) WillSetRestrictedDenoms(ctx sdk.Context, restrictedDenoms []string) { + // XXX nothing to do yet, nothing cached. + // XXX validate input. +} + +func (bank BankKeeper) RestrictedDenoms(ctx sdk.Context) []string { + params := bank.GetParams(ctx) + return params.RestrictedDenoms +} + +type stringSet map[string]struct{} + +func toSet(str []string) stringSet { + ss := stringSet{} + + for _, key := range str { + ss[key] = struct{}{} } + return ss } // InputOutputCoins handles a list of inputs and outputs @@ -50,6 +87,9 @@ func (bank BankKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs } for _, in := range inputs { + if !bank.canSendCoins(ctx, in.Address, in.Coins) { + return std.RestrictedTransferError{} + } _, err := bank.SubtractCoins(ctx, in.Address, in.Coins) if err != nil { return err @@ -84,8 +124,46 @@ func (bank BankKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs return nil } -// SendCoins moves coins from one account to another +// canSendCoins returns true if the coins can be sent without violating any restriction. +func (bank BankKeeper) canSendCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) bool { + rds := bank.RestrictedDenoms(ctx) + if len(rds) == 0 { + // No restrictions. + return true + } + if amt.ContainOneOfDenom(toSet(rds)) { + acc := bank.acck.GetAccount(ctx, addr) + accr, ok := acc.(std.AccountUnrestricter) + if ok && accr.IsUnrestricted() { + return true + } + return false + } + return true +} + +// SendCoins moves coins from one account to another, restrction could be applied func (bank BankKeeper) SendCoins(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { + // read restricted boolean value from param.IsRestrictedTransfer() + // canSendCoins is true until they have agreed to the waiver + if !bank.canSendCoins(ctx, fromAddr, amt) { + return std.RestrictedTransferError{} + } + + return bank.sendCoins(ctx, fromAddr, toAddr, amt) +} + +// SendCoinsUnrestricted is used for paying gas. +func (bank BankKeeper) SendCoinsUnrestricted(ctx sdk.Context, fromAddr crypto.Address, toAddr crypto.Address, amt std.Coins) error { + return bank.sendCoins(ctx, fromAddr, toAddr, amt) +} + +func (bank BankKeeper) sendCoins( + ctx sdk.Context, + fromAddr crypto.Address, + toAddr crypto.Address, + amt std.Coins, +) error { _, err := bank.SubtractCoins(ctx, fromAddr, amt) if err != nil { return err diff --git a/tm2/pkg/sdk/bank/keeper_test.go b/tm2/pkg/sdk/bank/keeper_test.go index df2039a682c..53f6a79926c 100644 --- a/tm2/pkg/sdk/bank/keeper_test.go +++ b/tm2/pkg/sdk/bank/keeper_test.go @@ -23,56 +23,56 @@ func TestKeeper(t *testing.T) { // Test GetCoins/SetCoins env.acck.SetAccount(ctx, acc) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins())) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins())) - env.bank.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10))) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) + env.bankk.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10))) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) // Test HasCoins - require.True(t, env.bank.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10)))) - require.True(t, env.bank.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 5)))) - require.False(t, env.bank.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15)))) - require.False(t, env.bank.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 5)))) + require.True(t, env.bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10)))) + require.True(t, env.bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 5)))) + require.False(t, env.bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15)))) + require.False(t, env.bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 5)))) // Test AddCoins - env.bank.AddCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15))) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 25)))) + env.bankk.AddCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15))) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 25)))) - env.bank.AddCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 15))) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 15), std.NewCoin("foocoin", 25)))) + env.bankk.AddCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 15))) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 15), std.NewCoin("foocoin", 25)))) // Test SubtractCoins - env.bank.SubtractCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10))) - env.bank.SubtractCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 5))) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 15)))) + env.bankk.SubtractCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10))) + env.bankk.SubtractCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 5))) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 15)))) - env.bank.SubtractCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 11))) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 15)))) + env.bankk.SubtractCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 11))) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 15)))) - env.bank.SubtractCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 10))) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 15)))) - require.False(t, env.bank.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 1)))) + env.bankk.SubtractCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 10))) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 15)))) + require.False(t, env.bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 1)))) // Test SendCoins - env.bank.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 5))) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) - require.True(t, env.bank.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("foocoin", 5)))) + env.bankk.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 5))) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) + require.True(t, env.bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("foocoin", 5)))) - _ = env.bank.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 50))) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) - require.True(t, env.bank.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("foocoin", 5)))) + _ = env.bankk.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 50))) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) + require.True(t, env.bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("foocoin", 5)))) - env.bank.AddCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 30))) - env.bank.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 5))) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 20), std.NewCoin("foocoin", 5)))) - require.True(t, env.bank.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 10)))) + env.bankk.AddCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 30))) + env.bankk.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 5))) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 20), std.NewCoin("foocoin", 5)))) + require.True(t, env.bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 10)))) // Test InputOutputCoins input1 := NewInput(addr2, std.NewCoins(std.NewCoin("foocoin", 2))) output1 := NewOutput(addr, std.NewCoins(std.NewCoin("foocoin", 2))) - env.bank.InputOutputCoins(ctx, []Input{input1}, []Output{output1}) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 20), std.NewCoin("foocoin", 7)))) - require.True(t, env.bank.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 8)))) + env.bankk.InputOutputCoins(ctx, []Input{input1}, []Output{output1}) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 20), std.NewCoin("foocoin", 7)))) + require.True(t, env.bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 8)))) inputs := []Input{ NewInput(addr, std.NewCoins(std.NewCoin("foocoin", 3))), @@ -83,10 +83,10 @@ func TestKeeper(t *testing.T) { NewOutput(addr, std.NewCoins(std.NewCoin("barcoin", 1))), NewOutput(addr3, std.NewCoins(std.NewCoin("barcoin", 2), std.NewCoin("foocoin", 5))), } - env.bank.InputOutputCoins(ctx, inputs, outputs) - require.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 21), std.NewCoin("foocoin", 4)))) - require.True(t, env.bank.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 7), std.NewCoin("foocoin", 6)))) - require.True(t, env.bank.GetCoins(ctx, addr3).IsEqual(std.NewCoins(std.NewCoin("barcoin", 2), std.NewCoin("foocoin", 5)))) + env.bankk.InputOutputCoins(ctx, inputs, outputs) + require.True(t, env.bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 21), std.NewCoin("foocoin", 4)))) + require.True(t, env.bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 7), std.NewCoin("foocoin", 6)))) + require.True(t, env.bankk.GetCoins(ctx, addr3).IsEqual(std.NewCoins(std.NewCoin("barcoin", 2), std.NewCoin("foocoin", 5)))) } func TestBankKeeper(t *testing.T) { @@ -95,7 +95,7 @@ func TestBankKeeper(t *testing.T) { env := setupTestEnv() ctx := env.ctx - bank := NewBankKeeper(env.acck) + bankk := env.bankk addr := crypto.AddressFromPreimage([]byte("addr1")) addr2 := crypto.AddressFromPreimage([]byte("addr2")) @@ -103,37 +103,37 @@ func TestBankKeeper(t *testing.T) { // Test GetCoins/SetCoins env.acck.SetAccount(ctx, acc) - require.True(t, bank.GetCoins(ctx, addr).IsEqual(std.NewCoins())) + require.True(t, bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins())) - env.bank.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10))) - require.True(t, bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) + env.bankk.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10))) + require.True(t, bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) // Test HasCoins - require.True(t, bank.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10)))) - require.True(t, bank.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 5)))) - require.False(t, bank.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15)))) - require.False(t, bank.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 5)))) + require.True(t, bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10)))) + require.True(t, bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 5)))) + require.False(t, bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15)))) + require.False(t, bankk.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 5)))) - env.bank.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15))) + env.bankk.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15))) // Test SendCoins - bank.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 5))) - require.True(t, bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) - require.True(t, bank.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("foocoin", 5)))) + bankk.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 5))) + require.True(t, bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) + require.True(t, bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("foocoin", 5)))) - err := bank.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 50))) - require.True(t, bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) - require.True(t, bank.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("foocoin", 5)))) + err := bankk.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("foocoin", 50))) + require.True(t, bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) + require.True(t, bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("foocoin", 5)))) - env.bank.AddCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 30))) - bank.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 5))) - require.True(t, bank.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 20), std.NewCoin("foocoin", 5)))) - require.True(t, bank.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 10)))) + env.bankk.AddCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 30))) + bankk.SendCoins(ctx, addr, addr2, std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 5))) + require.True(t, bankk.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("barcoin", 20), std.NewCoin("foocoin", 5)))) + require.True(t, bankk.GetCoins(ctx, addr2).IsEqual(std.NewCoins(std.NewCoin("barcoin", 10), std.NewCoin("foocoin", 10)))) // validate coins with invalid denoms or negative values cannot be sent // NOTE: We must use the Coin literal as the constructor does not allow // negative values. - err = bank.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.Coin{Denom: "FOOCOIN", Amount: -5}}) + err = bankk.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.Coin{Denom: "FOOCOIN", Amount: -5}}) require.Error(t, err) } @@ -151,7 +151,7 @@ func TestViewKeeper(t *testing.T) { env.acck.SetAccount(ctx, acc) require.True(t, view.GetCoins(ctx, addr).IsEqual(std.NewCoins())) - env.bank.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10))) + env.bankk.SetCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 10))) require.True(t, view.GetCoins(ctx, addr).IsEqual(std.NewCoins(std.NewCoin("foocoin", 10)))) // Test HasCoins @@ -160,3 +160,27 @@ func TestViewKeeper(t *testing.T) { require.False(t, view.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("foocoin", 15)))) require.False(t, view.HasCoins(ctx, addr, std.NewCoins(std.NewCoin("barcoin", 5)))) } + +// Test SetRestrictedDenoms +func TestSetRestrictedDenoms(t *testing.T) { + env := setupTestEnv() + ctx := env.ctx + bankk := env.bankk + prmk := env.prmk + // Add a single denom + prmk.SetStrings(ctx, "bank:p:restricted_denoms", []string{"foo"}) + params := bankk.GetParams(ctx) + require.Contains(t, params.RestrictedDenoms, "foo") + + // Add multiple denoms + prmk.SetStrings(ctx, "bank:p:restricted_denoms", []string{"goo", "bar"}) + params = bankk.GetParams(ctx) + require.NotContains(t, params.RestrictedDenoms, "foo") + require.Contains(t, params.RestrictedDenoms, "goo") + require.Contains(t, params.RestrictedDenoms, "bar") + + // Add empty list + prmk.SetStrings(ctx, "bank:p:restricted_denoms", []string{}) + params = bankk.GetParams(ctx) + require.Empty(t, params.RestrictedDenoms) +} diff --git a/tm2/pkg/sdk/bank/params.go b/tm2/pkg/sdk/bank/params.go new file mode 100644 index 00000000000..8d4dfa7fce3 --- /dev/null +++ b/tm2/pkg/sdk/bank/params.go @@ -0,0 +1,69 @@ +package bank + +import ( + "fmt" + "strings" + + "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/gnolang/gno/tm2/pkg/std" +) + +type BankParamsContextKey struct{} + +// Params defines the parameters for the bank module. +type Params struct { + RestrictedDenoms []string `json:"restricted_denoms" yaml:"restricted_denoms"` +} + +// NewParams creates a new Params object +func NewParams(restDenoms []string) Params { + return Params{ + RestrictedDenoms: restDenoms, + } +} + +// DefaultParams returns a default set of parameters. +func DefaultParams() Params { + return NewParams([]string{}) +} + +// String implements the stringer interface. +func (p Params) String() string { + var sb strings.Builder + sb.WriteString("Params: \n") + sb.WriteString(fmt.Sprintf("RestrictedDenom: %q\n", p.RestrictedDenoms)) + return sb.String() +} + +func (p *Params) Validate() error { + for _, denom := range p.RestrictedDenoms { + err := std.ValidateDenom(denom) + if err != nil { + return fmt.Errorf("invalid restricted denom: %s", denom) + } + } + return nil +} + +func (bank BankKeeper) SetParams(ctx sdk.Context, params Params) error { + if err := params.Validate(); err != nil { + return err + } + bank.prmk.SetStruct(ctx, "p", params) + return nil +} + +func (bank BankKeeper) GetParams(ctx sdk.Context) Params { + params := Params{} + bank.prmk.GetStruct(ctx, "p", ¶ms) + return params +} + +func (bank BankKeeper) WillSetParam(ctx sdk.Context, key string, value interface{}) { + switch key { + case "p:restricted_denoms": // XXX test + bank.WillSetRestrictedDenoms(ctx, value.([]string)) + default: + // Allow setting non-existent key. + } +} diff --git a/tm2/pkg/sdk/baseapp.go b/tm2/pkg/sdk/baseapp.go index 746dd618800..a1004f55eb3 100644 --- a/tm2/pkg/sdk/baseapp.go +++ b/tm2/pkg/sdk/baseapp.go @@ -646,6 +646,7 @@ func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Res ctx = ctx.WithEventLogger(NewEventLogger()) msgLogs := make([]string, 0, len(msgs)) + msgInfos := make([]string, 0, len(msgs)) data := make([]byte, 0, len(msgs)) var ( @@ -675,6 +676,7 @@ func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Res // each result. data = append(data, msgResult.Data...) events = append(events, msgResult.Events...) + msgInfos = append(msgInfos, msgResult.Info) // stop execution and return on first failed message if !msgResult.IsOK() { @@ -698,6 +700,7 @@ func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Res result.Error = ABCIError(err) result.Data = data result.Events = events + result.Info = strings.Join(msgInfos, "\n") result.Log = strings.Join(msgLogs, "\n") result.GasUsed = ctx.GasMeter().GasConsumed() return result diff --git a/tm2/pkg/sdk/options.go b/tm2/pkg/sdk/options.go index b9840a7510b..97a87bb0921 100644 --- a/tm2/pkg/sdk/options.go +++ b/tm2/pkg/sdk/options.go @@ -53,7 +53,7 @@ func (app *BaseApp) SetDB(db dbm.DB) { func (app *BaseApp) SetCMS(cms store.CommitMultiStore) { if app.sealed { - panic("SetEndBlocker() on sealed BaseApp") + panic("SetCMS() on sealed BaseApp") } app.cms = cms } diff --git a/tm2/pkg/sdk/params/amino_helper.go b/tm2/pkg/sdk/params/amino_helper.go new file mode 100644 index 00000000000..6ae087fea07 --- /dev/null +++ b/tm2/pkg/sdk/params/amino_helper.go @@ -0,0 +1,80 @@ +package params + +import ( + "reflect" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/std" + sm "github.com/gnolang/gno/tm2/pkg/store" +) + +// Returns list of kvpairs from param struct. +func encodeStructFields(prm interface{}) (res []std.KVPair) { + rvPrm := reflect.ValueOf(prm) + tinfo, err := amino.GetTypeInfo(rvPrm.Type()) + if err != nil { + panic(errors.Wrap(err, "Error reflecting on module param struct")) + } + fields := tinfo.StructInfo.Fields + for i, field := range fields { + rv := rvPrm.Field(i) + name := field.JSONName + value := amino.MustMarshalJSON(rv.Interface()) + res = append(res, std.KVPair{Key: []byte(name), Value: value}) + } + return res +} + +func findKV(kvz []std.KVPair, key string) (std.KVPair, bool) { + for _, kv := range kvz { + if string(kv.Key) == key { + return kv, true + } + } + return std.KVPair{}, false +} + +// Reads list of kvpairs into param struct. +func decodeStructFields(prmPtr interface{}, kvz []std.KVPair) { + if reflect.TypeOf(prmPtr).Kind() != reflect.Pointer { + panic("setStructFields expects module param struct pointer") + } + rvPrm := reflect.ValueOf(prmPtr).Elem() + tinfo, err := amino.GetTypeInfo(rvPrm.Type()) + if err != nil { + panic(errors.Wrap(err, "Error reflecting on module param struct")) + } + fields := tinfo.StructInfo.Fields + for i, field := range fields { + rv := rvPrm.Field(i) + name := field.JSONName + kv, ok := findKV(kvz, name) + if !ok { + continue + } + amino.MustUnmarshalJSON(kv.Value, rv.Addr().Interface()) + } +} + +// Gets list of kvpairs associated with param struct from store. +func getStructFieldsFromStore(prmPtr interface{}, store sm.Store, key []byte) (res []std.KVPair) { + if reflect.TypeOf(prmPtr).Kind() != reflect.Pointer { + panic("setStructFields expects module param struct pointer") + } + rvPrm := reflect.ValueOf(prmPtr).Elem() + tinfo, err := amino.GetTypeInfo(rvPrm.Type()) + if err != nil { + panic(errors.Wrap(err, "Error reflecting on module param struct")) + } + fields := tinfo.StructInfo.Fields + for _, field := range fields { + name := field.JSONName + value := store.Get([]byte(string(key) + ":" + name)) + if value == nil { + continue + } + res = append(res, std.KVPair{Key: []byte(name), Value: value}) + } + return res +} diff --git a/tm2/pkg/sdk/params/doc.go b/tm2/pkg/sdk/params/doc.go index a433b5eb115..4048c6da4ea 100644 --- a/tm2/pkg/sdk/params/doc.go +++ b/tm2/pkg/sdk/params/doc.go @@ -4,12 +4,34 @@ // It includes a keeper for managing key-value pairs with module identifiers as // prefixes, along with a global querier for retrieving any key from any module. // -// Changes: This version removes the concepts of subspaces and proposals, -// allowing the creation of multiple keepers identified by a provided prefix. -// Proposals may be added later when governance modules are introduced. The -// transient store and .Modified helper have also been removed but can be -// implemented later if needed. Keys are represented as strings instead of -// []byte. -// -// XXX: removes isAlphaNum validation for keys. +// The Params Module provides functionalities for caching and persistent access +// to parameters across the entire chain. +// +// Keys are generally of the format ::. +// Parameters are stored in the underlying store with key format +// /pv/::. +// +// * 'module' must be an alphanumeric ASCII string. +// * 'submodule' can be anything but cannot contain a ':' +// * 'submodule' is set to 'p' for keeper param structs. +// * The VM keeper of gno.land uses the submodule for realm paths. +// +// Module parameters are sourced from all other keepers, such as AuthKeeper, +// BankKeeper, and VMKeeper which must be registered with .Register(). +// The ParamsKeeper ensures that the is registered, +// but otherwise doesn't enforce much else about the key format. +// +// Before params are written ParamfulKeeper.WillSetParam() is called, allowing +// any custom caching to happen if needed. Any caching must be stored in 'ctx', +// and keepers must otherwise be immutable otherwise the checktx (mempool) and +// delivertx (blockchain) states would trample each other. +// +// ParamKeeper.SetStruct(module, "p", k, v) is used by each registered module +// keeper to set the module parameter struct (for type-safety). +// ParamKeeper.SetParamXXX() is used to set arbitrary primitive parameters. +// +// The method for querying parameters follows this pattern: +// gnokey query params/::. +// For example, gnokey query params/vm:gno.land/r/myuser/myrealm:foo. + package params diff --git a/tm2/pkg/sdk/params/handler.go b/tm2/pkg/sdk/params/handler.go index b662fc06c58..10c5ba7c470 100644 --- a/tm2/pkg/sdk/params/handler.go +++ b/tm2/pkg/sdk/params/handler.go @@ -5,6 +5,7 @@ import ( "strings" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -24,35 +25,43 @@ func (bh paramsHandler) Process(ctx sdk.Context, msg std.Msg) sdk.Result { return abciResult(std.ErrUnknownRequest(errMsg)) } -//---------------------------------------- -// Query - +// ---------------------------------------- +// Query: +// - params/prefix:key for a prefixed module parameter key. +// - params/key for an arbitrary parameter key. func (bh paramsHandler) Query(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - switch secondPart(req.Path) { - case bh.params.prefix: - return bh.queryParam(ctx, req) - default: - res = sdk.ABCIResponseQueryFromError( - std.ErrUnknownRequest("unknown params query endpoint")) - return + parts := strings.SplitN(req.Path, "/", 2) + var path, rest string + if len(parts) == 0 { + // return helpful instructions. + } else if len(parts) == 1 { + path = parts[0] + rest = "" + } else { + path = parts[0] + rest = parts[1] } -} + switch path { + case "params": + module, err := moduleOf(rest) + if err != nil { + res = sdk.ABCIResponseQueryFromError(err) + return + } + if !bh.params.ModuleExists(module) { + res = sdk.ABCIResponseQueryFromError( + std.ErrUnknownRequest(fmt.Sprintf("module not registered: %q", module))) + return + } + val := bh.params.GetRaw(ctx, rest) + res.Data = val + return -// queryParam returns param for a key. -func (bh paramsHandler) queryParam(ctx sdk.Context, req abci.RequestQuery) (res abci.ResponseQuery) { - // parse key from path. - key := thirdPartWithSlashes(req.Path) - if key == "" { + default: res = sdk.ABCIResponseQueryFromError( - std.ErrUnknownRequest("param key is empty")) + std.ErrUnknownRequest(fmt.Sprintf("unknown params query endpoint %q", path))) + return } - - // XXX: validate? - - val := bh.params.GetRaw(ctx, key) - - res.Data = val - return } //---------------------------------------- @@ -62,18 +71,16 @@ func abciResult(err error) sdk.Result { return sdk.ABCIResultFromError(err) } -// returns the second component of a path. -func secondPart(path string) string { - parts := strings.SplitN(path, "/", 3) +// extracts the module portion of a key +// of the format :: +func moduleOf(key string) (module string, err error) { + parts := strings.SplitN(key, ":", 2) if len(parts) < 2 { - return "" - } else { - return parts[1] + return "", errors.New("expected param key format :, but got %q", key) } -} - -// returns the third component of a path, including other slashes. -func thirdPartWithSlashes(path string) string { - split := strings.SplitN(path, "/", 3) - return split[2] + module = parts[0] + if len(module) == 0 { + return "", errors.New("expected param key format :, but got %q", key) + } + return module, nil } diff --git a/tm2/pkg/sdk/params/handler_test.go b/tm2/pkg/sdk/params/handler_test.go index 071eb12b52b..cc9f6581005 100644 --- a/tm2/pkg/sdk/params/handler_test.go +++ b/tm2/pkg/sdk/params/handler_test.go @@ -21,7 +21,7 @@ func TestInvalidMsg(t *testing.T) { require.True(t, strings.Contains(res.Log, "unrecognized params message type")) } -func TestQuery(t *testing.T) { +func TestArbitraryParamsQuery(t *testing.T) { t.Parallel() env := setupTestEnv() @@ -31,11 +31,11 @@ func TestQuery(t *testing.T) { path string expected string }{ - {path: "params/params_test/foo/bar.string", expected: `"baz"`}, - {path: "params/params_test/foo/bar.int64", expected: `"-12345"`}, - {path: "params/params_test/foo/bar.uint64", expected: `"4242"`}, - {path: "params/params_test/foo/bar.bool", expected: "true"}, - {path: "params/params_test/foo/bar.bytes", expected: `"YmF6"`}, + {path: "params/" + dummyModuleName + ":bar_string", expected: `"baz"`}, + {path: "params/" + dummyModuleName + ":bar_int64", expected: `"-12345"`}, + {path: "params/" + dummyModuleName + ":bar_uint64", expected: `"4242"`}, + {path: "params/" + dummyModuleName + ":bar_bool", expected: "true"}, + {path: "params/" + dummyModuleName + ":bar_bytes", expected: `"YmF6"`}, } for _, tc := range tcs { @@ -48,11 +48,11 @@ func TestQuery(t *testing.T) { require.Nil(t, res.Data) } - env.keeper.SetString(env.ctx, "foo/bar.string", "baz") - env.keeper.SetInt64(env.ctx, "foo/bar.int64", -12345) - env.keeper.SetUint64(env.ctx, "foo/bar.uint64", 4242) - env.keeper.SetBool(env.ctx, "foo/bar.bool", true) - env.keeper.SetBytes(env.ctx, "foo/bar.bytes", []byte("baz")) + env.keeper.SetString(env.ctx, dummyModuleName+":bar_string", "baz") + env.keeper.SetInt64(env.ctx, dummyModuleName+":bar_int64", -12345) + env.keeper.SetUint64(env.ctx, dummyModuleName+":bar_uint64", 4242) + env.keeper.SetBool(env.ctx, dummyModuleName+":bar_bool", true) + env.keeper.SetBytes(env.ctx, dummyModuleName+":bar_bytes", []byte("baz")) for _, tc := range tcs { req := abci.RequestQuery{ @@ -61,17 +61,59 @@ func TestQuery(t *testing.T) { res := h.Query(env.ctx, req) require.Nil(t, res.Error) require.NotNil(t, res) - assert.Equal(t, string(res.Data), tc.expected) + assert.Equal(t, tc.expected, string(res.Data)) } } -func TestQuerierRouteNotFound(t *testing.T) { +func TestModuleParamsQuery(t *testing.T) { t.Parallel() + env := setupTestEnv() + h := NewHandler(env.keeper) + tcs := []struct { + path string + expected string + }{ + {path: "params/params_test:foo/bar.string", expected: `"baz"`}, + {path: "params/params_test:foo/bar.int64", expected: `"-12345"`}, + {path: "params/params_test:foo/bar.uint64", expected: `"4242"`}, + {path: "params/params_test:foo/bar.bool", expected: "true"}, + {path: "params/params_test:foo/bar.bytes", expected: `"YmF6"`}, + } + + for _, tc := range tcs { + req := abci.RequestQuery{ + Path: tc.path, + } + res := h.Query(env.ctx, req) + require.Nil(t, res.Error) + require.NotNil(t, res) + require.Nil(t, res.Data) + } + + env.keeper.SetString(env.ctx, "params_test:foo/bar.string", "baz") + env.keeper.SetInt64(env.ctx, "params_test:foo/bar.int64", -12345) + env.keeper.SetUint64(env.ctx, "params_test:foo/bar.uint64", 4242) + env.keeper.SetBool(env.ctx, "params_test:foo/bar.bool", true) + env.keeper.SetBytes(env.ctx, "params_test:foo/bar.bytes", []byte("baz")) + + for _, tc := range tcs { + req := abci.RequestQuery{ + Path: tc.path, + } + res := h.Query(env.ctx, req) + require.Nil(t, res.Error) + require.NotNil(t, res) + assert.Equal(t, tc.expected, string(res.Data)) + } +} + +func TestQuerierRouteNotFound(t *testing.T) { + t.Parallel() env := setupTestEnv() h := NewHandler(env.keeper) req := abci.RequestQuery{ - Path: "params/notfound", + Path: "params/notfound:", Data: []byte{}, } res := h.Query(env.ctx, req) diff --git a/tm2/pkg/sdk/params/keeper.go b/tm2/pkg/sdk/params/keeper.go index c99b9dbfde1..0f9ae7afecb 100644 --- a/tm2/pkg/sdk/params/keeper.go +++ b/tm2/pkg/sdk/params/keeper.go @@ -1,7 +1,11 @@ package params +// XXX Rename ParamsKeeper to ParamKeeper, like AccountKeeper is singular. + import ( + "fmt" "log/slog" + "reflect" "strings" "github.com/gnolang/gno/tm2/pkg/amino" @@ -12,13 +16,12 @@ import ( const ( ModuleName = "params" - StoreKey = ModuleName - // ValueStorePrevfix is "/pv/" for param value. - ValueStoreKeyPrefix = "/pv/" + // StoreKey = ModuleName + StoreKeyPrefix = "/pv/" ) -func ValueStoreKey(key string) []byte { - return append([]byte(ValueStoreKeyPrefix), []byte(key)...) +func storeKey(key string) []byte { + return append([]byte(StoreKeyPrefix), []byte(key)...) } type ParamsKeeperI interface { @@ -27,57 +30,74 @@ type ParamsKeeperI interface { GetUint64(ctx sdk.Context, key string, ptr *uint64) GetBool(ctx sdk.Context, key string, ptr *bool) GetBytes(ctx sdk.Context, key string, ptr *[]byte) + GetStrings(ctx sdk.Context, key string, ptr *[]string) SetString(ctx sdk.Context, key string, value string) SetInt64(ctx sdk.Context, key string, value int64) SetUint64(ctx sdk.Context, key string, value uint64) SetBool(ctx sdk.Context, key string, value bool) SetBytes(ctx sdk.Context, key string, value []byte) + SetStrings(ctx sdk.Context, key string, value []string) Has(ctx sdk.Context, key string) bool GetRaw(ctx sdk.Context, key string) []byte + SetRaw(ctx sdk.Context, key string, value []byte) + + GetStruct(ctx sdk.Context, key string, strctPtr interface{}) + SetStruct(ctx sdk.Context, key string, strct interface{}) + + // NOTE: GetAny and SetAny don't work on structs. + GetAny(ctx sdk.Context, key string) interface{} + SetAny(ctx sdk.Context, key string, value interface{}) +} - // XXX: ListKeys? +type ParamfulKeeper interface { + WillSetParam(ctx sdk.Context, key string, value interface{}) } var _ ParamsKeeperI = ParamsKeeper{} // global paramstore Keeper. type ParamsKeeper struct { - key store.StoreKey - prefix string + key store.StoreKey + kprs map[string]ParamfulKeeper // Register a prefix for module parameter keys. } // NewParamsKeeper returns a new ParamsKeeper. -func NewParamsKeeper(key store.StoreKey, prefix string) ParamsKeeper { +func NewParamsKeeper(key store.StoreKey) ParamsKeeper { return ParamsKeeper{ - key: key, - prefix: prefix, + key: key, + kprs: map[string]ParamfulKeeper{}, } } -// GetParam gets a param value from the global param store. -func (pk ParamsKeeper) GetParams(ctx sdk.Context, key string, target interface{}) (bool, error) { - stor := ctx.Store(pk.key) +func (pk ParamsKeeper) ForModule(moduleName string) prefixParamsKeeper { + ppk := newPrefixParamsKeeper(pk, moduleName+":") + return ppk +} - bz := stor.Get(ValueStoreKey(key)) - if bz == nil { - return false, nil +func (pk ParamsKeeper) GetRegisteredKeeper(moduleName string) ParamfulKeeper { + rk, ok := pk.kprs[moduleName] + if !ok { + panic("keeper for module " + moduleName + " not registered") } - - return true, amino.UnmarshalJSON(bz, target) + return rk } -// SetParam sets a param value to the global param store. -func (pk ParamsKeeper) SetParams(ctx sdk.Context, key string, param interface{}) error { - stor := ctx.Store(pk.key) - bz, err := amino.MarshalJSON(param) - if err != nil { - return err +func (pk ParamsKeeper) Register(moduleName string, pmk ParamfulKeeper) { + if _, exists := pk.kprs[moduleName]; exists { + panic("keeper for module " + moduleName + " already registered") } + pk.kprs[moduleName] = pmk +} + +func (pk ParamsKeeper) IsRegistered(moduleName string) bool { + _, ok := pk.kprs[moduleName] + return ok +} - stor.Set(ValueStoreKey(key), bz) - return nil +func (pk ParamsKeeper) ModuleExists(moduleName string) bool { + return pk.IsRegistered(moduleName) } // XXX: why do we expose this? @@ -87,101 +107,254 @@ func (pk ParamsKeeper) Logger(ctx sdk.Context) *slog.Logger { func (pk ParamsKeeper) Has(ctx sdk.Context, key string) bool { stor := ctx.Store(pk.key) - return stor.Has([]byte(key)) -} - -func (pk ParamsKeeper) GetRaw(ctx sdk.Context, key string) []byte { - stor := ctx.Store(pk.key) - return stor.Get([]byte(key)) + return stor.Has(storeKey(key)) } func (pk ParamsKeeper) GetString(ctx sdk.Context, key string, ptr *string) { - checkSuffix(key, ".string") pk.getIfExists(ctx, key, ptr) } func (pk ParamsKeeper) GetBool(ctx sdk.Context, key string, ptr *bool) { - checkSuffix(key, ".bool") pk.getIfExists(ctx, key, ptr) } func (pk ParamsKeeper) GetInt64(ctx sdk.Context, key string, ptr *int64) { - checkSuffix(key, ".int64") pk.getIfExists(ctx, key, ptr) } func (pk ParamsKeeper) GetUint64(ctx sdk.Context, key string, ptr *uint64) { - checkSuffix(key, ".uint64") pk.getIfExists(ctx, key, ptr) } func (pk ParamsKeeper) GetBytes(ctx sdk.Context, key string, ptr *[]byte) { - checkSuffix(key, ".bytes") + pk.getIfExists(ctx, key, ptr) +} + +func (pk ParamsKeeper) GetStrings(ctx sdk.Context, key string, ptr *[]string) { pk.getIfExists(ctx, key, ptr) } func (pk ParamsKeeper) SetString(ctx sdk.Context, key, value string) { - checkSuffix(key, ".string") pk.set(ctx, key, value) } func (pk ParamsKeeper) SetBool(ctx sdk.Context, key string, value bool) { - checkSuffix(key, ".bool") pk.set(ctx, key, value) } func (pk ParamsKeeper) SetInt64(ctx sdk.Context, key string, value int64) { - checkSuffix(key, ".int64") pk.set(ctx, key, value) } func (pk ParamsKeeper) SetUint64(ctx sdk.Context, key string, value uint64) { - checkSuffix(key, ".uint64") pk.set(ctx, key, value) } func (pk ParamsKeeper) SetBytes(ctx sdk.Context, key string, value []byte) { - checkSuffix(key, ".bytes") pk.set(ctx, key, value) } -func (pk ParamsKeeper) getIfExists(ctx sdk.Context, key string, ptr interface{}) { +func (pk ParamsKeeper) SetStrings(ctx sdk.Context, key string, value []string) { + pk.set(ctx, key, value) +} + +func (pk ParamsKeeper) GetRaw(ctx sdk.Context, key string) []byte { stor := ctx.Store(pk.key) - bz := stor.Get([]byte(key)) - if bz == nil { - return + return stor.Get(storeKey(key)) +} + +func (pk ParamsKeeper) SetRaw(ctx sdk.Context, key string, value []byte) { + stor := ctx.Store(pk.key) + stor.Set(storeKey(key), value) +} + +func (pk ParamsKeeper) GetStruct(ctx sdk.Context, key string, strctPtr interface{}) { + parts := strings.Split(key, ":") + if len(parts) != 2 { + panic("struct key expected format :") } - err := amino.UnmarshalJSON(bz, ptr) - if err != nil { - panic(err) + moduleName := parts[0] + structName := parts[1] // + if !pk.IsRegistered(moduleName) { + panic("unregistered module name") } + if structName != "p" { + panic("the only supported struct name is 'p'") + } + stor := ctx.Store(pk.key) + kvz := getStructFieldsFromStore(strctPtr, stor, storeKey(key)) + decodeStructFields(strctPtr, kvz) } -func (pk ParamsKeeper) get(ctx sdk.Context, key string, ptr interface{}) { +func (pk ParamsKeeper) SetStruct(ctx sdk.Context, key string, strct interface{}) { + parts := strings.Split(key, ":") + if len(parts) != 2 { + panic("struct key expected format :") + } + moduleName := parts[0] + structName := parts[1] // + if !pk.IsRegistered(moduleName) { + panic("unregistered module name") + } + if structName != "p" { + panic("the only supported struct name is 'p'") + } stor := ctx.Store(pk.key) - bz := stor.Get([]byte(key)) - err := amino.UnmarshalJSON(bz, ptr) - if err != nil { - panic(err) + kvz := encodeStructFields(strct) + for _, kv := range kvz { + stor.Set(storeKey(key+":"+string(kv.Key)), kv.Value) } } +func (pk ParamsKeeper) GetAny(ctx sdk.Context, key string) interface{} { + panic("not yet implemented") +} + +func (pk ParamsKeeper) SetAny(ctx sdk.Context, key string, value interface{}) { + switch value := value.(type) { + case string: + pk.SetString(ctx, key, value) + case int64: + pk.SetInt64(ctx, key, value) + case uint64: + pk.SetUint64(ctx, key, value) + case bool: + pk.SetBool(ctx, key, value) + case []byte: + pk.SetBytes(ctx, key, value) + case []string: + pk.SetStrings(ctx, key, value) + default: + panic(fmt.Sprintf("unexected value type for SetAny: %v", reflect.TypeOf(value))) + } +} + +func (pk ParamsKeeper) getIfExists(ctx sdk.Context, key string, ptr interface{}) { + stor := ctx.Store(pk.key) + bz := stor.Get(storeKey(key)) + if bz == nil { + return + } + amino.MustUnmarshalJSON(bz, ptr) +} + func (pk ParamsKeeper) set(ctx sdk.Context, key string, value interface{}) { + module, rawKey := parsePrefix(key) + if module != "" { + kpr := pk.GetRegisteredKeeper(module) + if kpr != nil { + kpr.WillSetParam(ctx, rawKey, value) + } + } stor := ctx.Store(pk.key) - bz, err := amino.MarshalJSON(value) - if err != nil { - panic(err) + bz := amino.MustMarshalJSON(value) + stor.Set(storeKey(key), bz) +} + +func parsePrefix(key string) (prefix, rawKey string) { + // Look for the first colon. + colonIndex := strings.Index(key, ":") + + if colonIndex != -1 { + // colon found: the key has a module prefix. + prefix = key[:colonIndex] + rawKey = key[colonIndex+1:] + + return } - stor.Set([]byte(key), bz) + return "", key } -func checkSuffix(key, expectedSuffix string) { - var ( - noSuffix = !strings.HasSuffix(key, expectedSuffix) - noName = len(key) == len(expectedSuffix) - // XXX: additional sanity checks? - ) - if noSuffix || noName { - panic(`key should be like "` + expectedSuffix + `" (` + key + `)`) +//---------------------------------------- + +type prefixParamsKeeper struct { + prefix string + pk ParamsKeeper +} + +func newPrefixParamsKeeper(pk ParamsKeeper, prefix string) prefixParamsKeeper { + return prefixParamsKeeper{ + prefix: prefix, + pk: pk, } } + +func (ppk prefixParamsKeeper) prefixed(key string) string { + return ppk.prefix + key +} + +func (ppk prefixParamsKeeper) GetString(ctx sdk.Context, key string, ptr *string) { + ppk.pk.GetString(ctx, ppk.prefixed(key), ptr) +} + +func (ppk prefixParamsKeeper) GetInt64(ctx sdk.Context, key string, ptr *int64) { + ppk.pk.GetInt64(ctx, ppk.prefixed(key), ptr) +} + +func (ppk prefixParamsKeeper) GetUint64(ctx sdk.Context, key string, ptr *uint64) { + ppk.pk.GetUint64(ctx, ppk.prefixed(key), ptr) +} + +func (ppk prefixParamsKeeper) GetBool(ctx sdk.Context, key string, ptr *bool) { + ppk.pk.GetBool(ctx, ppk.prefixed(key), ptr) +} + +func (ppk prefixParamsKeeper) GetBytes(ctx sdk.Context, key string, ptr *[]byte) { + ppk.pk.GetBytes(ctx, ppk.prefixed(key), ptr) +} + +func (ppk prefixParamsKeeper) GetStrings(ctx sdk.Context, key string, ptr *[]string) { + ppk.pk.GetStrings(ctx, ppk.prefixed(key), ptr) +} + +func (ppk prefixParamsKeeper) SetString(ctx sdk.Context, key string, value string) { + ppk.pk.SetString(ctx, ppk.prefixed(key), value) +} + +func (ppk prefixParamsKeeper) SetInt64(ctx sdk.Context, key string, value int64) { + ppk.pk.SetInt64(ctx, ppk.prefixed(key), value) +} + +func (ppk prefixParamsKeeper) SetUint64(ctx sdk.Context, key string, value uint64) { + ppk.pk.SetUint64(ctx, ppk.prefixed(key), value) +} + +func (ppk prefixParamsKeeper) SetBool(ctx sdk.Context, key string, value bool) { + ppk.pk.SetBool(ctx, ppk.prefixed(key), value) +} + +func (ppk prefixParamsKeeper) SetBytes(ctx sdk.Context, key string, value []byte) { + ppk.pk.SetBytes(ctx, ppk.prefixed(key), value) +} + +func (ppk prefixParamsKeeper) SetStrings(ctx sdk.Context, key string, value []string) { + ppk.pk.SetStrings(ctx, ppk.prefixed(key), value) +} + +func (ppk prefixParamsKeeper) Has(ctx sdk.Context, key string) bool { + return ppk.pk.Has(ctx, ppk.prefixed(key)) +} + +func (ppk prefixParamsKeeper) GetRaw(ctx sdk.Context, key string) []byte { + return ppk.pk.GetRaw(ctx, ppk.prefixed(key)) +} + +func (ppk prefixParamsKeeper) SetRaw(ctx sdk.Context, key string, value []byte) { + ppk.pk.SetRaw(ctx, ppk.prefixed(key), value) +} + +func (ppk prefixParamsKeeper) GetStruct(ctx sdk.Context, key string, paramPtr interface{}) { + ppk.pk.GetStruct(ctx, ppk.prefixed(key), paramPtr) +} + +func (ppk prefixParamsKeeper) SetStruct(ctx sdk.Context, key string, param interface{}) { + ppk.pk.SetStruct(ctx, ppk.prefixed(key), param) +} + +func (ppk prefixParamsKeeper) GetAny(ctx sdk.Context, key string) interface{} { + return ppk.pk.GetAny(ctx, ppk.prefixed(key)) +} + +func (ppk prefixParamsKeeper) SetAny(ctx sdk.Context, key string, value interface{}) { + ppk.pk.SetAny(ctx, ppk.prefixed(key), value) +} diff --git a/tm2/pkg/sdk/params/keeper_test.go b/tm2/pkg/sdk/params/keeper_test.go index aedfaa9d5a3..3a883d2da1a 100644 --- a/tm2/pkg/sdk/params/keeper_test.go +++ b/tm2/pkg/sdk/params/keeper_test.go @@ -13,24 +13,24 @@ func TestKeeper(t *testing.T) { ctx, store, keeper := env.ctx, env.store, env.keeper _ = store // XXX: add store tests? - require.False(t, keeper.Has(ctx, "param1.string")) - require.False(t, keeper.Has(ctx, "param2.bool")) - require.False(t, keeper.Has(ctx, "param3.uint64")) - require.False(t, keeper.Has(ctx, "param4.int64")) - require.False(t, keeper.Has(ctx, "param5.bytes")) + require.False(t, keeper.Has(ctx, "param1")) + require.False(t, keeper.Has(ctx, "param2")) + require.False(t, keeper.Has(ctx, "param3")) + require.False(t, keeper.Has(ctx, "param4")) + require.False(t, keeper.Has(ctx, "param5")) // initial set - require.NotPanics(t, func() { keeper.SetString(ctx, "param1.string", "foo") }) - require.NotPanics(t, func() { keeper.SetBool(ctx, "param2.bool", true) }) - require.NotPanics(t, func() { keeper.SetUint64(ctx, "param3.uint64", 42) }) - require.NotPanics(t, func() { keeper.SetInt64(ctx, "param4.int64", -1337) }) - require.NotPanics(t, func() { keeper.SetBytes(ctx, "param5.bytes", []byte("hello world!")) }) - - require.True(t, keeper.Has(ctx, "param1.string")) - require.True(t, keeper.Has(ctx, "param2.bool")) - require.True(t, keeper.Has(ctx, "param3.uint64")) - require.True(t, keeper.Has(ctx, "param4.int64")) - require.True(t, keeper.Has(ctx, "param5.bytes")) + require.NotPanics(t, func() { keeper.SetString(ctx, "param1", "foo") }) + require.NotPanics(t, func() { keeper.SetBool(ctx, "param2", true) }) + require.NotPanics(t, func() { keeper.SetUint64(ctx, "param3", 42) }) + require.NotPanics(t, func() { keeper.SetInt64(ctx, "param4", -1337) }) + require.NotPanics(t, func() { keeper.SetBytes(ctx, "param5", []byte("hello world!")) }) + + require.True(t, keeper.Has(ctx, "param1")) + require.True(t, keeper.Has(ctx, "param2")) + require.True(t, keeper.Has(ctx, "param3")) + require.True(t, keeper.Has(ctx, "param4")) + require.True(t, keeper.Has(ctx, "param5")) var ( param1 string @@ -40,11 +40,11 @@ func TestKeeper(t *testing.T) { param5 []byte ) - require.NotPanics(t, func() { keeper.GetString(ctx, "param1.string", ¶m1) }) - require.NotPanics(t, func() { keeper.GetBool(ctx, "param2.bool", ¶m2) }) - require.NotPanics(t, func() { keeper.GetUint64(ctx, "param3.uint64", ¶m3) }) - require.NotPanics(t, func() { keeper.GetInt64(ctx, "param4.int64", ¶m4) }) - require.NotPanics(t, func() { keeper.GetBytes(ctx, "param5.bytes", ¶m5) }) + require.NotPanics(t, func() { keeper.GetString(ctx, "param1", ¶m1) }) + require.NotPanics(t, func() { keeper.GetBool(ctx, "param2", ¶m2) }) + require.NotPanics(t, func() { keeper.GetUint64(ctx, "param3", ¶m3) }) + require.NotPanics(t, func() { keeper.GetInt64(ctx, "param4", ¶m4) }) + require.NotPanics(t, func() { keeper.GetBytes(ctx, "param5", ¶m5) }) require.Equal(t, param1, "foo") require.Equal(t, param2, true) @@ -53,36 +53,29 @@ func TestKeeper(t *testing.T) { require.Equal(t, param5, []byte("hello world!")) // reset - require.NotPanics(t, func() { keeper.SetString(ctx, "param1.string", "bar") }) - require.NotPanics(t, func() { keeper.SetBool(ctx, "param2.bool", false) }) - require.NotPanics(t, func() { keeper.SetUint64(ctx, "param3.uint64", 12345) }) - require.NotPanics(t, func() { keeper.SetInt64(ctx, "param4.int64", 1000) }) - require.NotPanics(t, func() { keeper.SetBytes(ctx, "param5.bytes", []byte("bye")) }) - - require.True(t, keeper.Has(ctx, "param1.string")) - require.True(t, keeper.Has(ctx, "param2.bool")) - require.True(t, keeper.Has(ctx, "param3.uint64")) - require.True(t, keeper.Has(ctx, "param4.int64")) - require.True(t, keeper.Has(ctx, "param5.bytes")) - - require.NotPanics(t, func() { keeper.GetString(ctx, "param1.string", ¶m1) }) - require.NotPanics(t, func() { keeper.GetBool(ctx, "param2.bool", ¶m2) }) - require.NotPanics(t, func() { keeper.GetUint64(ctx, "param3.uint64", ¶m3) }) - require.NotPanics(t, func() { keeper.GetInt64(ctx, "param4.int64", ¶m4) }) - require.NotPanics(t, func() { keeper.GetBytes(ctx, "param5.bytes", ¶m5) }) + require.NotPanics(t, func() { keeper.SetString(ctx, "param1", "bar") }) + require.NotPanics(t, func() { keeper.SetBool(ctx, "param2", false) }) + require.NotPanics(t, func() { keeper.SetUint64(ctx, "param3", 12345) }) + require.NotPanics(t, func() { keeper.SetInt64(ctx, "param4", 1000) }) + require.NotPanics(t, func() { keeper.SetBytes(ctx, "param5", []byte("bye")) }) + + require.True(t, keeper.Has(ctx, "param1")) + require.True(t, keeper.Has(ctx, "param2")) + require.True(t, keeper.Has(ctx, "param3")) + require.True(t, keeper.Has(ctx, "param4")) + require.True(t, keeper.Has(ctx, "param5")) + + require.NotPanics(t, func() { keeper.GetString(ctx, "param1", ¶m1) }) + require.NotPanics(t, func() { keeper.GetBool(ctx, "param2", ¶m2) }) + require.NotPanics(t, func() { keeper.GetUint64(ctx, "param3", ¶m3) }) + require.NotPanics(t, func() { keeper.GetInt64(ctx, "param4", ¶m4) }) + require.NotPanics(t, func() { keeper.GetBytes(ctx, "param5", ¶m5) }) require.Equal(t, param1, "bar") require.Equal(t, param2, false) require.Equal(t, param3, uint64(12345)) require.Equal(t, param4, int64(1000)) require.Equal(t, param5, []byte("bye")) - - // invalid sets - require.PanicsWithValue(t, `key should be like ".string" (invalid.int64)`, func() { keeper.SetString(ctx, "invalid.int64", "hello") }) - require.PanicsWithValue(t, `key should be like ".int64" (invalid.string)`, func() { keeper.SetInt64(ctx, "invalid.string", int64(42)) }) - require.PanicsWithValue(t, `key should be like ".uint64" (invalid.int64)`, func() { keeper.SetUint64(ctx, "invalid.int64", uint64(42)) }) - require.PanicsWithValue(t, `key should be like ".bool" (invalid.int64)`, func() { keeper.SetBool(ctx, "invalid.int64", true) }) - require.PanicsWithValue(t, `key should be like ".bytes" (invalid.int64)`, func() { keeper.SetBytes(ctx, "invalid.int64", []byte("hello")) }) } // adapted from TestKeeperSubspace from Cosmos SDK, but adapted to a subspace-less Keeper. @@ -114,22 +107,14 @@ func TestKeeper_internal(t *testing.T) { for i, kv := range kvs { require.NotPanics(t, func() { keeper.getIfExists(ctx, "invalid", kv.ptr) }, "keeper.GetIfExists panics when no value exists, tc #%d", i) require.Equal(t, kv.zero, indirect(kv.ptr), "keeper.GetIfExists unmarshalls when no value exists, tc #%d", i) - require.Panics(t, func() { keeper.get(ctx, "invalid", kv.ptr) }, "invalid keeper.Get not panics when no value exists, tc #%d", i) - require.Equal(t, kv.zero, indirect(kv.ptr), "invalid keeper.Get unmarshalls when no value exists, tc #%d", i) - require.NotPanics(t, func() { keeper.getIfExists(ctx, kv.key, kv.ptr) }, "keeper.GetIfExists panics, tc #%d", i) require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) - require.NotPanics(t, func() { keeper.get(ctx, kv.key, kv.ptr) }, "keeper.Get panics, tc #%d", i) - require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) - - require.Panics(t, func() { keeper.get(ctx, "invalid", kv.ptr) }, "invalid keeper.Get not panics when no value exists, tc #%d", i) - require.Equal(t, kv.param, indirect(kv.ptr), "invalid keeper.Get unmarshalls when no value existt, tc #%d", i) - - require.Panics(t, func() { keeper.get(ctx, kv.key, nil) }, "invalid keeper.Get not panics when the pointer is nil, tc #%d", i) + require.Panics(t, func() { keeper.getIfExists(ctx, kv.key, nil) }, "invalid keeper.Get not panics when the pointer is nil, tc #%d", i) } for i, kv := range kvs { - bz := store.Get([]byte(kv.key)) + vk := storeKey(kv.key) + bz := store.Get(vk) require.NotNil(t, bz, "store.Get() returns nil, tc #%d", i) err := amino.UnmarshalJSON(bz, kv.ptr) require.NoError(t, err, "cdc.UnmarshalJSON() returns error, tc #%d", i) @@ -146,18 +131,16 @@ type Params struct { p2 string } -func TestGetAndSetParams(t *testing.T) { +func TestGetAndSetStruct(t *testing.T) { env := setupTestEnv() ctx := env.ctx keeper := env.keeper - // SetParams + // SetStruct a := Params{p1: 1, p2: "a"} - err := keeper.SetParams(ctx, ModuleName, a) - require.NoError(t, err) + keeper.SetStruct(ctx, "params_test:p", a) - // GetParams + // GetStruct a1 := Params{} - _, err1 := keeper.GetParams(ctx, ModuleName, &a1) - require.NoError(t, err1) + keeper.GetStruct(ctx, "params_test:p", &a1) require.True(t, amino.DeepEqual(a, a1), "a and a1 should equal") } diff --git a/gno.land/pkg/gnoland/param_test.go b/tm2/pkg/sdk/params/param_test.go similarity index 53% rename from gno.land/pkg/gnoland/param_test.go rename to tm2/pkg/sdk/params/param_test.go index 5d17aab40da..50136e0e2fc 100644 --- a/gno.land/pkg/gnoland/param_test.go +++ b/tm2/pkg/sdk/params/param_test.go @@ -1,4 +1,4 @@ -package gnoland +package params import ( "testing" @@ -14,11 +14,12 @@ func TestParam_Parse(t *testing.T) { expected Param expectErr bool }{ - {"valid string", "foo.string=hello", Param{key: "foo", kind: "string", value: "hello"}, false}, - {"valid int64", "foo.int64=-1337", Param{key: "foo", kind: "int64", value: int64(-1337)}, false}, - {"valid uint64", "foo.uint64=42", Param{key: "foo", kind: "uint64", value: uint64(42)}, false}, - {"valid bool", "foo.bool=true", Param{key: "foo", kind: "bool", value: true}, false}, - {"valid bytes", "foo.bytes=AAAA", Param{key: "foo", kind: "bytes", value: []byte{0xaa, 0xaa}}, false}, + {"valid string", "foo.string=hello", Param{Key: "foo", Type: "string", Value: "hello"}, false}, + {"valid int64", "foo.int64=-1337", Param{Key: "foo", Type: "int64", Value: int64(-1337)}, false}, + {"valid uint64", "foo.uint64=42", Param{Key: "foo", Type: "uint64", Value: uint64(42)}, false}, + {"valid bool", "foo.bool=true", Param{Key: "foo", Type: "bool", Value: true}, false}, + {"valid bytes", "foo.bytes=AAAA", Param{Key: "foo", Type: "bytes", Value: []byte{0xaa, 0xaa}}, false}, + {"valid strings", "foo.strings=some,strings", Param{Key: "foo", Type: "strings", Value: []string{"some", "strings"}}, false}, {"invalid key", "invalidkey=foo", Param{}, true}, {"invalid kind", "invalid.kind=foo", Param{}, true}, {"invalid int64", "invalid.int64=foobar", Param{}, true}, diff --git a/tm2/pkg/sdk/params/params_test.go b/tm2/pkg/sdk/params/params_test.go new file mode 100644 index 00000000000..4270fd65c55 --- /dev/null +++ b/tm2/pkg/sdk/params/params_test.go @@ -0,0 +1,63 @@ +package params + +import ( + "testing" + + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/stretchr/testify/require" +) + +// newModuleParams defines the parameters with updated fields for a module. +type newModuleParams struct { + LimitedTokens []string `json:"limited_tokens" yaml:"limited_tokens"` + Max uint64 `json:"max" yaml:"max"` +} + +// oldModuleParams defines the parameters for a module. +type oldModuleParams struct { + LimitedTokens []string `json:"limited_tokens" yaml:"limited_tokens"` +} + +// newOldModuleParams creates a new oldModuleParams object. +func newOldModuleParams(tokens []string) oldModuleParams { + return oldModuleParams{ + LimitedTokens: tokens, + } +} + +func TestBackwardCompatibility(t *testing.T) { + oldParams := newOldModuleParams([]string{"token1", "token2"}) + + // Serialize oldModuleParams to JSON + bz, err := amino.MarshalJSON(oldParams) + require.NoError(t, err, "Failed to marshal oldModuleParams") + + t.Logf("Serialized oldModuleParams: %s\n", bz) + + // Deserialize JSON into newModuleParams + newParams := &newModuleParams{} + err = amino.UnmarshalJSON(bz, newParams) + require.NoError(t, err, "Failed to unmarshal into newModuleParams") + + // Validate compatibility + require.Equal(t, oldParams.LimitedTokens, newParams.LimitedTokens, "LimitedTokens mismatch") + require.Equal(t, uint64(0), newParams.Max, "Max should default to 0 in backward compatibility") +} + +func TestForwardCompatibility(t *testing.T) { + newParams := newModuleParams{LimitedTokens: []string{"token1", "token2"}, Max: 10} + + // Serialize newModuleParams to JSON + bz, err := amino.MarshalJSON(newParams) + require.NoError(t, err, "Failed to marshal newModuleParams") + + t.Logf("Serialized newModuleParams: %s\n", bz) + + // Deserialize JSON into oldModuleParams + oldParams := &oldModuleParams{} + err = amino.UnmarshalJSON(bz, oldParams) + require.NoError(t, err, "Failed to unmarshal into oldModuleParams") + + // Validate compatibility + require.Equal(t, newParams.LimitedTokens, oldParams.LimitedTokens, "LimitedTokens mismatch") +} diff --git a/tm2/pkg/sdk/params/test_common.go b/tm2/pkg/sdk/params/test_common.go index 8243ee867de..45b0241b6e1 100644 --- a/tm2/pkg/sdk/params/test_common.go +++ b/tm2/pkg/sdk/params/test_common.go @@ -6,6 +6,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/gnolang/gno/tm2/pkg/store" "github.com/gnolang/gno/tm2/pkg/store/iavl" ) @@ -23,8 +24,9 @@ func setupTestEnv() testEnv { ms.MountStoreWithDB(paramsCapKey, iavl.StoreConstructor, db) ms.LoadLatestVersion() - prefix := "params_test" - keeper := NewParamsKeeper(paramsCapKey, prefix) + prmk := NewParamsKeeper(paramsCapKey) + dk := NewDummyKeeper(prmk.ForModule(dummyModuleName)) + prmk.Register(dummyModuleName, dk) ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{Height: 1, ChainID: "test-chain-id"}, log.NewNoopLogger()) // XXX: context key? @@ -42,5 +44,21 @@ func setupTestEnv() testEnv { }) stor := ctx.Store(paramsCapKey) - return testEnv{ctx: ctx, store: stor, keeper: keeper} + return testEnv{ctx: ctx, store: stor, keeper: prmk} +} + +const dummyModuleName = "params_test" + +type DummyKeeper struct { + prmk ParamsKeeperI +} + +func NewDummyKeeper(prmk ParamsKeeperI) DummyKeeper { + return DummyKeeper{ + prmk: prmk, + } +} + +func (dk DummyKeeper) WillSetParam(ctx sdk.Context, key string, value interface{}) { + // do nothing } diff --git a/tm2/pkg/sdk/params/types.go b/tm2/pkg/sdk/params/types.go new file mode 100644 index 00000000000..f1e41f56164 --- /dev/null +++ b/tm2/pkg/sdk/params/types.go @@ -0,0 +1,129 @@ +package params + +import ( + "encoding/hex" + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +type Param struct { + Key string + Type string + Value interface{} +} + +const ( + ParamTypeString = "string" + ParamTypeInt64 = "int64" + ParamTypeUint64 = "uint64" + ParamTypeBool = "bool" + ParamTypeBytes = "bytes" + ParamTypeStrings = "strings" +) + +// NOTE: do not support structs here. +func NewParam(key string, value interface{}) Param { + switch value.(type) { + case string: + return Param{Key: key, Type: ParamTypeString, Value: value} + case int64: + return Param{Key: key, Type: ParamTypeInt64, Value: value} + case uint64: + return Param{Key: key, Type: ParamTypeUint64, Value: value} + case bool: + return Param{Key: key, Type: ParamTypeBool, Value: value} + case []byte: + return Param{Key: key, Type: ParamTypeBytes, Value: value} + case []string: + return Param{Key: key, Type: ParamTypeStrings, Value: value} + default: + panic(fmt.Sprintf("unexpected param value type %v", reflect.TypeOf(value))) + } +} + +func (p Param) ValidateBasic() error { + // XXX: validate type and value + return nil +} + +// As would appear in genesis.json. +func (p *Param) Parse(entry string) error { + parts := strings.SplitN(strings.TrimSpace(entry), "=", 2) // .= + if len(parts) != 2 { + return fmt.Errorf("malformed entry: %q", entry) + } + + keyWithType := parts[0] + rawValue := parts[1] + p.Type = keyWithType[strings.LastIndex(keyWithType, ".")+1:] + p.Key = strings.TrimSuffix(keyWithType, "."+p.Type) + switch p.Type { + case ParamTypeString: + p.Value = rawValue + case ParamTypeInt64: + v, err := strconv.ParseInt(rawValue, 10, 64) + if err != nil { + return err + } + p.Value = v + case ParamTypeBool: + v, err := strconv.ParseBool(rawValue) + if err != nil { + return err + } + p.Value = v + case ParamTypeUint64: + v, err := strconv.ParseUint(rawValue, 10, 64) + if err != nil { + return err + } + p.Value = v + case ParamTypeBytes: + v, err := hex.DecodeString(rawValue) + if err != nil { + return err + } + p.Value = v + case ParamTypeStrings: + parts := strings.Split(rawValue, ",") + p.Value = parts + default: + return errors.New("unsupported param type: " + p.Type + " (" + entry + ")") + } + + return p.ValidateBasic() +} + +func (p Param) String() string { + typedKey := p.Key + "." + p.Type + switch p.Type { + case ParamTypeString: + return fmt.Sprintf("%s=%s", typedKey, p.Value) + case ParamTypeInt64: + return fmt.Sprintf("%s=%d", typedKey, p.Value) + case ParamTypeUint64: + return fmt.Sprintf("%s=%d", typedKey, p.Value) + case ParamTypeBool: + if p.Value.(bool) { + return fmt.Sprintf("%s=true", typedKey) + } + return fmt.Sprintf("%s=false", typedKey) + case ParamTypeBytes: + return fmt.Sprintf("%s=%x", typedKey, p.Value) + case ParamTypeStrings: + valstr := strings.Join(p.Value.([]string), ",") + return fmt.Sprintf("%s=%s", typedKey, valstr) + } + panic("invalid param type:" + p.Type) +} + +func (p *Param) UnmarshalAmino(rep string) error { + return p.Parse(rep) +} + +func (p Param) MarshalAmino() (string, error) { + return p.String(), nil +} diff --git a/tm2/pkg/service/service.go b/tm2/pkg/service/service.go index 05f7a4f4ae6..c93eb06b298 100644 --- a/tm2/pkg/service/service.go +++ b/tm2/pkg/service/service.go @@ -159,7 +159,7 @@ func (bs *BaseService) OnStart() error { return nil } func (bs *BaseService) Stop() error { if atomic.CompareAndSwapUint32(&bs.stopped, 0, 1) { if atomic.LoadUint32(&bs.started) == 0 { - bs.Logger.Error(fmt.Sprintf("Not stopping %v -- have not been started yet", bs.name), "impl", bs.impl) + bs.Logger.Warn(fmt.Sprintf("Not stopping %v -- have not been started yet", bs.name), "impl", bs.impl) // revert flag atomic.StoreUint32(&bs.stopped, 0) return ErrNotStarted diff --git a/tm2/pkg/std/account.go b/tm2/pkg/std/account.go index c70f43d22e9..35cd116ea3b 100644 --- a/tm2/pkg/std/account.go +++ b/tm2/pkg/std/account.go @@ -38,6 +38,10 @@ type Account interface { String() string } +type AccountUnrestricter interface { + IsUnrestricted() bool +} + //---------------------------------------- // BaseAccount diff --git a/tm2/pkg/std/coin.go b/tm2/pkg/std/coin.go index fba20a5ba78..d1d20e110f8 100644 --- a/tm2/pkg/std/coin.go +++ b/tm2/pkg/std/coin.go @@ -63,7 +63,7 @@ func (coin Coin) String() string { // validate returns an error if the Coin has a negative amount or if // the denom is invalid. func validate(denom string, amount int64) error { - if err := validateDenom(denom); err != nil { + if err := ValidateDenom(denom); err != nil { return err } @@ -229,7 +229,7 @@ func (coins Coins) IsValid() bool { case 0: return true case 1: - if err := validateDenom(coins[0].Denom); err != nil { + if err := ValidateDenom(coins[0].Denom); err != nil { return false } return coins[0].IsPositive() @@ -328,6 +328,21 @@ func (coins Coins) AddUnsafe(coinsB Coins) Coins { } } +// ContainOneOfDenom check if a Coins instance contains a denom in the provided denomos +func (coins Coins) ContainOneOfDenom(denoms map[string]struct{}) bool { + if len(denoms) == 0 { + return false + } + + for _, coin := range coins { + if _, ok := denoms[coin.Denom]; ok && coin.IsPositive() { + return true + } + } + + return false +} + // DenomsSubsetOf returns true if receiver's denom set // is subset of coinsB's denoms. func (coins Coins) DenomsSubsetOf(coinsB Coins) bool { @@ -623,7 +638,7 @@ var ( reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnmString)) ) -func validateDenom(denom string) error { +func ValidateDenom(denom string) error { if !reDnm.MatchString(denom) { return fmt.Errorf("invalid denom: %s", denom) } @@ -631,7 +646,7 @@ func validateDenom(denom string) error { } func mustValidateDenom(denom string) { - if err := validateDenom(denom); err != nil { + if err := ValidateDenom(denom); err != nil { panic(err) } } @@ -661,7 +676,7 @@ func ParseCoin(coinStr string) (coin Coin, err error) { return Coin{}, errors.Wrapf(err, "failed to parse coin amount: %s", amountStr) } - if err := validateDenom(denomStr); err != nil { + if err := ValidateDenom(denomStr); err != nil { return Coin{}, fmt.Errorf("invalid denom cannot contain upper case characters or spaces: %w", err) } diff --git a/tm2/pkg/std/coin_test.go b/tm2/pkg/std/coin_test.go index 33ee7425a0c..ba3c8f49c54 100644 --- a/tm2/pkg/std/coin_test.go +++ b/tm2/pkg/std/coin_test.go @@ -730,3 +730,23 @@ func TestMarshalJSONCoins(t *testing.T) { }) } } + +func TestContainOneOfDenom(t *testing.T) { + restrictList := map[string]struct{}{ + "baz": {}, + "foo": {}, + } + amt := Coins{ + {"foo", int64(1)}, + {"bar", int64(1)}, + } + require.True(t, amt.ContainOneOfDenom(restrictList)) + + zero := Coins{ + {"foo", int64(0)}, + {"bar", int64(1)}, + } + + // only return true when the value is posible + require.False(t, zero.ContainOneOfDenom(restrictList)) +} diff --git a/tm2/pkg/std/errors.go b/tm2/pkg/std/errors.go index 715b27b3eb4..48bd93be1de 100644 --- a/tm2/pkg/std/errors.go +++ b/tm2/pkg/std/errors.go @@ -14,43 +14,45 @@ func (abciError) AssertABCIError() {} type InternalError struct{ abciError } type ( - TxDecodeError struct{ abciError } - InvalidSequenceError struct{ abciError } - UnauthorizedError struct{ abciError } - InsufficientFundsError struct{ abciError } - UnknownRequestError struct{ abciError } - InvalidAddressError struct{ abciError } - UnknownAddressError struct{ abciError } - InvalidPubKeyError struct{ abciError } - InsufficientCoinsError struct{ abciError } - InvalidCoinsError struct{ abciError } - InvalidGasWantedError struct{ abciError } - OutOfGasError struct{ abciError } - MemoTooLargeError struct{ abciError } - InsufficientFeeError struct{ abciError } - TooManySignaturesError struct{ abciError } - NoSignaturesError struct{ abciError } - GasOverflowError struct{ abciError } + TxDecodeError struct{ abciError } + InvalidSequenceError struct{ abciError } + UnauthorizedError struct{ abciError } + InsufficientFundsError struct{ abciError } + UnknownRequestError struct{ abciError } + InvalidAddressError struct{ abciError } + UnknownAddressError struct{ abciError } + InvalidPubKeyError struct{ abciError } + InsufficientCoinsError struct{ abciError } + InvalidCoinsError struct{ abciError } + InvalidGasWantedError struct{ abciError } + OutOfGasError struct{ abciError } + MemoTooLargeError struct{ abciError } + InsufficientFeeError struct{ abciError } + TooManySignaturesError struct{ abciError } + NoSignaturesError struct{ abciError } + GasOverflowError struct{ abciError } + RestrictedTransferError struct{ abciError } ) -func (e InternalError) Error() string { return "internal error" } -func (e TxDecodeError) Error() string { return "tx decode error" } -func (e InvalidSequenceError) Error() string { return "invalid sequence error" } -func (e UnauthorizedError) Error() string { return "unauthorized error" } -func (e InsufficientFundsError) Error() string { return "insufficient funds error" } -func (e UnknownRequestError) Error() string { return "unknown request error" } -func (e InvalidAddressError) Error() string { return "invalid address error" } -func (e UnknownAddressError) Error() string { return "unknown address error" } -func (e InvalidPubKeyError) Error() string { return "invalid pubkey error" } -func (e InsufficientCoinsError) Error() string { return "insufficient coins error" } -func (e InvalidCoinsError) Error() string { return "invalid coins error" } -func (e InvalidGasWantedError) Error() string { return "invalid gas wanted" } -func (e OutOfGasError) Error() string { return "out of gas error" } -func (e MemoTooLargeError) Error() string { return "memo too large error" } -func (e InsufficientFeeError) Error() string { return "insufficient fee error" } -func (e TooManySignaturesError) Error() string { return "too many signatures error" } -func (e NoSignaturesError) Error() string { return "no signatures error" } -func (e GasOverflowError) Error() string { return "gas overflow error" } +func (e InternalError) Error() string { return "internal error" } +func (e TxDecodeError) Error() string { return "tx decode error" } +func (e InvalidSequenceError) Error() string { return "invalid sequence error" } +func (e UnauthorizedError) Error() string { return "unauthorized error" } +func (e InsufficientFundsError) Error() string { return "insufficient funds error" } +func (e UnknownRequestError) Error() string { return "unknown request error" } +func (e InvalidAddressError) Error() string { return "invalid address error" } +func (e UnknownAddressError) Error() string { return "unknown address error" } +func (e InvalidPubKeyError) Error() string { return "invalid pubkey error" } +func (e InsufficientCoinsError) Error() string { return "insufficient coins error" } +func (e InvalidCoinsError) Error() string { return "invalid coins error" } +func (e InvalidGasWantedError) Error() string { return "invalid gas wanted" } +func (e OutOfGasError) Error() string { return "out of gas error" } +func (e MemoTooLargeError) Error() string { return "memo too large error" } +func (e InsufficientFeeError) Error() string { return "insufficient fee error" } +func (e TooManySignaturesError) Error() string { return "too many signatures error" } +func (e NoSignaturesError) Error() string { return "no signatures error" } +func (e GasOverflowError) Error() string { return "gas overflow error" } +func (e RestrictedTransferError) Error() string { return "restricted token transfer error" } // NOTE also update pkg/std/package.go registrations. diff --git a/tm2/pkg/std/package.go b/tm2/pkg/std/package.go index a1aadc17cb6..471c32f3f5e 100644 --- a/tm2/pkg/std/package.go +++ b/tm2/pkg/std/package.go @@ -36,4 +36,5 @@ var Package = amino.RegisterPackage(amino.NewPackage( TooManySignaturesError{}, "TooManySignaturesError", NoSignaturesError{}, "NoSignaturesError", GasOverflowError{}, "GasOverflowError", + RestrictedTransferError{}, "RestrictedTransferError", )) diff --git a/tm2/pkg/store/types/gas.go b/tm2/pkg/store/types/gas.go index a86cff17d1a..8afbed698ea 100644 --- a/tm2/pkg/store/types/gas.go +++ b/tm2/pkg/store/types/gas.go @@ -79,7 +79,7 @@ func (g *basicGasMeter) Limit() Gas { } func (g *basicGasMeter) Remaining() Gas { - return g.Limit() - g.GasConsumedToLimit() + return overflow.Sub64p(g.Limit(), g.GasConsumedToLimit()) } func (g *basicGasMeter) GasConsumedToLimit() Gas {